New upstream version 8.1.0

This commit is contained in:
geos_one
2025-08-10 01:34:16 +02:00
commit c891bb7105
4398 changed files with 838833 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
#include "Common.h"
#ifdef BEEGFS_DEBUG
#include <common/app/AbstractApp.h>
#include <common/app/log/Logger.h>
#include <execinfo.h>
#include <sstream>
namespace beegfs_debug
{
void assertMsg(const char* file, unsigned line, const char* condition)
{
std::stringstream message;
const char* basename = strrchr(file, '/');
if (!basename)
basename = file;
else
basename++;
message << "ASSERTION TRIGGERED AT " << basename << ":" << line << '\n';
message << '\n';
message << condition;
message << '\n';
message << '\n';
message << "Backtrace of asserting thread ";
message << PThread::getCurrentThreadName();
message << ":";
std::vector<void*> backtrace(32);
int backtraceSize;
backtraceSize = ::backtrace(&backtrace[0], (int) backtrace.size());
while (backtraceSize == (int) backtrace.size())
{
backtrace.resize(backtraceSize * 2);
backtraceSize = ::backtrace(&backtrace[0], (int) backtrace.size());
}
char** symbols = backtrace_symbols(&backtrace[0], backtraceSize);
for (int i = 0; i < backtraceSize; i++)
message << '\n' << i << ":\t" << symbols[i];
LOG(GENERAL,ERR, message.str());
exit(1);
}
}
#endif

View File

@@ -0,0 +1,345 @@
#pragma once
// define certain macros for (u)int64_t in inttypes.h; this is useful for printf on 32-/64-bit
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif // __STDC_FORMAT_MACROS
#include <chrono>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <inttypes.h>
#include <iostream>
#include <iomanip> // output formating
#include <fstream>
#include <sstream>
#include <pthread.h>
#include <map>
#include <set>
#include <list>
#include <vector>
#include <stdexcept>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cerrno>
#include <unistd.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <sys/poll.h>
#include <netdb.h>
#include <algorithm>
#include <sys/time.h>
#include <fcntl.h>
#include <boost/smart_ptr/make_unique.hpp>
#include <memory>
/**
* NOTE: These timeouts can now be overridden by the connMessagingTimeouts
* option in the configuration file. If that option is unset or set to <=0, we
* still default to these constants.
*/
#define CONN_LONG_TIMEOUT 600000
#define CONN_MEDIUM_TIMEOUT 90000
#define CONN_SHORT_TIMEOUT 30000
typedef std::map<std::string, std::string> StringMap;
typedef StringMap::iterator StringMapIter;
typedef StringMap::const_iterator StringMapCIter;
typedef StringMap::value_type StringMapVal;
typedef std::map<std::string, int64_t> StringInt64Map;
typedef StringInt64Map::iterator StringInt64MapIter;
typedef StringInt64Map::const_iterator StringInt64MapCIter;
typedef StringInt64Map::value_type StringInt64MapVal;
typedef std::set<std::string> StringSet;
typedef StringSet::iterator StringSetIter;
typedef StringSet::const_iterator StringSetCIter;
typedef std::list<std::string> StringList;
typedef StringList::iterator StringListIter;
typedef StringList::const_iterator StringListConstIter;
typedef std::vector<std::string> StringVector;
typedef StringVector::iterator StringVectorIter;
typedef StringVector::const_iterator StringVectorConstIter;
typedef std::list<uint8_t> UInt8List;
typedef UInt8List::iterator UInt8ListIter;
typedef UInt8List::const_iterator UInt8ListConstIter;
typedef std::set<uint16_t> UInt16Set;
typedef UInt16Set::iterator UInt16SetIter;
typedef UInt16Set::const_iterator UInt16SetCIter;
typedef std::list<uint16_t> UInt16List;
typedef UInt16List::iterator UInt16ListIter;
typedef UInt16List::const_iterator UInt16ListConstIter;
typedef std::vector<uint16_t> UInt16Vector;
typedef UInt16Vector::iterator UInt16VectorIter;
typedef UInt16Vector::const_iterator UInt16VectorConstIter;
typedef std::vector<UInt16Set> UInt16SetVector;
typedef UInt16SetVector::iterator UInt16SetVectorIter;
typedef UInt16SetVector::const_iterator UInt16SetVectorConstIter;
typedef std::vector<UInt16List> UInt16ListVector;
typedef std::set<int> IntSet;
typedef IntSet::iterator IntSetIter;
typedef IntSet::const_iterator IntSetCIter;
typedef std::set<unsigned> UIntSet;
typedef UIntSet::iterator UIntSetIter;
typedef UIntSet::const_iterator UIntSetCIter;
typedef std::list<int> IntList;
typedef IntList::iterator IntListIter;
typedef IntList::const_iterator IntListConstIter;
typedef std::list<unsigned> UIntList;
typedef UIntList::iterator UIntListIter;
typedef UIntList::const_iterator UIntListConstIter;
typedef std::vector<unsigned> UIntVector;
typedef UIntVector::iterator UIntVectorIter;
typedef UIntVector::const_iterator UIntVectorConstIter;
typedef std::vector<int> IntVector;
typedef IntVector::iterator IntVectorIter;
typedef IntVector::const_iterator IntVectorConstIter;
typedef std::list<short> ShortList;
typedef ShortList::iterator ShortListIter;
typedef ShortList::const_iterator ShortListConstIter;
typedef std::list<unsigned short> UShortList;
typedef UShortList::iterator UShortListIter;
typedef UShortList::const_iterator UShortListConstIter;
typedef std::vector<short> ShortVector;
typedef ShortVector::iterator ShortVectorIter;
typedef ShortVector::const_iterator ShortVectorConstIter;
typedef std::vector<unsigned short> UShortVector;
typedef UShortVector::iterator UShortVectorIter;
typedef UShortVector::const_iterator UShortVectorConstIter;
typedef std::vector<char> CharVector;
typedef CharVector::iterator CharVectorIter;
typedef CharVector::const_iterator CharVectorConstIter;
typedef std::list<int64_t> Int64List;
typedef Int64List::iterator Int64ListIter;
typedef Int64List::const_iterator Int64ListConstIter;
typedef std::list<uint64_t> UInt64List;
typedef UInt64List::iterator UInt64ListIter;
typedef UInt64List::const_iterator UInt64ListConstIter;
typedef std::vector<int64_t> Int64Vector;
typedef Int64Vector::iterator Int64VectorIter;
typedef Int64Vector::const_iterator Int64VectorConstIter;
typedef std::vector<uint64_t> UInt64Vector;
typedef UInt64Vector::iterator UInt64VectorIter;
typedef UInt64Vector::const_iterator UInt64VectorConstIter;
typedef std::list<bool> BoolList;
typedef BoolList::iterator BoolListIter;
typedef BoolList::const_iterator BoolListConstIter;
#define BEEGFS_MIN(a, b) \
( ( (a) < (b) ) ? (a) : (b) )
#define BEEGFS_MAX(a, b) \
( ( (a) < (b) ) ? (b) : (a) )
#define SAFE_DELETE(p) \
do{ if(p) {delete (p); (p)=NULL;} } while(0)
#define SAFE_FREE(p) \
do{ if(p) {free(p); (p)=NULL;} } while(0)
#define SAFE_DELETE_NOSET(p) \
do{ if(p) delete (p); } while(0)
#define SAFE_FREE_NOSET(p) \
do{ if(p) free(p); } while(0)
// typically used for optional out-args
#define SAFE_ASSIGN(destPointer, sourceValue) \
do{ if(destPointer) {*(destPointer) = (sourceValue);} } while(0)
// gcc branch optimization hints
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
// this macro mutes warnings about unused variables
#define IGNORE_UNUSED_VARIABLE(a) do{ if( ((long)a)==1) {} } while(0)
// this macro mutes warnings about unsused variables that are only used in debug build
#ifdef BEEGFS_DEBUG
#define IGNORE_UNUSED_DEBUG_VARIABLE(a) do{ /* do nothing */ } while(0)
#else
#define IGNORE_UNUSED_DEBUG_VARIABLE(a) do{ if( ((long)a)==1) {} } while(0)
#endif
// dumps stack in case of a buggy condition
#define BEEGFS_BUG_ON(condition, msgStr) \
do { \
if(unlikely(condition) ) { \
fprintf(stderr, "%s:%d: %s\n", __func__, __LINE__, msgStr); \
LogContext(std::string(__func__) + StringTk::intToStr(__LINE__) ).logErr( \
std::string("BUG: ") + msgStr + std::string(" (dumping stack...)") ); \
LogContext(std::string(__func__) + StringTk::intToStr(__LINE__) ).logBacktrace(); \
} \
} while(0)
#ifdef LOG_DEBUG_MESSAGES
#define BEEGFS_BUG_ON_DEBUG(condition, msgStr) BEEGFS_BUG_ON(condition, msgStr)
#else // !LOG_DEBUG_MESSAGES
#define BEEGFS_BUG_ON_DEBUG(condition, msgStr) \
do { /* nothing */ } while(0)
#endif // LOG_DEBUG_MESSAGES
//logtype to enable syslog
enum LogType
{
LogType_SYSLOG=0,
LogType_LOGFILE
};
#ifdef BEEGFS_DEBUG
#define BEEGFS_DEBUG_PROFILING
#endif
// defined `override` (from c++11) if the compiler does not provide it. gcc >= 4.7 has it,
// clang since 3.0.
// we don't support gcc <= 4.4, so don't check those.
#if (!__clang__ && __GNUC__ == 4 && __GNUC_MINOR__ < 7) || (__clang__ && __clang_major__ < 3)
#define override
#endif
// gcc 4.4 does not seem to support eq-operators for enum classes. other old gcc versions may have
// this problem.
#if (!__clang__ && __GNUC__ == 4 && __GNUC_MINOR__ == 4 && __GNUC_PATCHLEVEL__ == 0)
# define GCC_COMPAT_ENUM_CLASS_OPEQNEQ(TYPE) \
inline bool operator==(TYPE a, TYPE b) { return memcmp(&a, &b, sizeof(a)) == 0; } \
inline bool operator!=(TYPE a, TYPE b) { return !(a == b); }
#else
# define GCC_COMPAT_ENUM_CLASS_OPEQNEQ(TYPE)
#endif
// define a nullptr type and value (also from c++11) if the compiler does not provide it.
// gcc >= 4.6 has it, as does clang >= 3.0.
// this merely approximates the actual nullptr behaviour from c++11, but it approximates it close
// enough to be useful.
// the implementation is taken from N2431, which proposes nullptr as a language feature.
#if (!__clang__ && __GNUC__ == 4 && __GNUC_MINOR__ < 6) || (__clang__ && __clang_major__ < 3)
struct nullptr_t
{
template<typename T>
operator T*() const { return 0; }
template<typename C, typename T>
operator T C::*() const { return 0; }
template<typename T>
operator std::unique_ptr<T>() const { return {}; }
template<typename T, typename Deleter>
operator std::unique_ptr<T, Deleter>() const { return {}; }
template<typename T>
operator std::shared_ptr<T>() const { return {}; }
template<typename T>
operator std::weak_ptr<T>() const { return {}; }
void operator&() = delete;
// add a bool-ish conversion operator to allow `if (nullptr)` (e.g. in templates)
typedef void (nullptr_t::*bool_type)();
operator bool_type() const { return 0; }
};
#define nullptr ((const nullptr_t)nullptr_t{})
#endif
#if (!__clang__ && __GNUC__ == 4 && __GNUC_MINOR__ < 6) || (__clang__ && __clang_major__ < 3)
# define noexcept(...)
#endif
// libstdc++ <= 4.6 calls std::chrono::steady_clock monotonic_clock, set alias
#if __GLIBCXX__ && __GLIBCXX__ < 20120322
namespace std
{
namespace chrono
{
typedef monotonic_clock steady_clock;
}
}
#endif
/*
* Optional definitions:
* - BEEGFS_NO_LICENSE_CHECK: Disables all license checking at startup and runtime
*/
/*
* Debug definitions:
* - BEEGFS_DEBUG: Generally defined for debug builds. Might turn on some other debug definitions
* below automatically.
*
* - LOG_DEBUG_MESSAGES: Enables logging of some extra debug messages that will not be
* available otherwise
* - DEBUG_REFCOUNT: Enables debugging of ObjectReferencer::refCount. Error messages will
* be logged if refCount is less than zero
* - DEBUG_MUTEX_LOCKING: Enables the debug functionality of the SafeRWLock class
* - BEEGFS_DEBUG_PROFILING: Additional timestamps in log messages.
*/
#ifdef BEEGFS_DEBUG
namespace beegfs_debug
{
__attribute__((noreturn))
extern void assertMsg(const char* file, unsigned line, const char* condition);
}
# define ASSERT(condition) \
do { \
if (!(condition)) \
::beegfs_debug::assertMsg(__FILE__, __LINE__, #condition); \
} while (0)
#else
# define ASSERT(cond) do {} while (0)
#endif
#if defined (__GLIBC__) && (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 24)
#define USE_READDIR_R 0
#else
#define USE_READDIR_R 1
#endif
#if __GNUC__ > 6
# define BEEGFS_FALLTHROUGH [[fallthrough]]
#else
# define BEEGFS_FALLTHROUGH
#endif
// XXX not sure about this. C++17 is the requirement for [[nodiscard]]
#define BEEGFS_NODISCARD [[nodiscard]]
// version number of both the network protocol and the on-disk data structures that are versioned.
// must be kept in sync with client.
#define BEEGFS_DATA_VERSION (uint32_t(0))

View File

@@ -0,0 +1,197 @@
#pragma once
#include <common/toolkit/serialization/Serialization.h>
#include <common/toolkit/StringTk.h>
#include <ios>
#include <sstream>
/*
* template class to represent a Numeric ID; which can be used as Numeric Node ID or Group ID, etc.
*
* we use classes instead of a simple typedef to achieve "type" safety with nodeIDs, groupIDs,
* targetIDs, etc.
*
* implemented as template because different kinds of IDs might have different base data types
*/
template <typename T, typename Tag>
class NumericID final
{
public:
typedef T ValueT;
NumericID():
value(0) { }
explicit NumericID(T value):
value(value) { }
T val() const
{
return value;
}
std::string str() const
{
std::stringstream out;
out << value;
return out.str();
}
std::string strHex() const
{
std::stringstream out;
out << std::hex << std::uppercase << value;
return out.str();
}
void fromHexStr(const std::string& valueStr)
{
std::stringstream in(valueStr);
in >> std::hex >> value;
}
void fromStr(const std::string& valueStr)
{
std::stringstream in(valueStr);
in >> value;
}
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx % obj->value;
}
bool operator==(const NumericID& other) const
{
return (value == other.value);
}
bool operator!=(const NumericID& other) const
{
return (value != other.value);
}
bool operator<(const NumericID& other) const
{
return (value < other.value);
}
bool operator>(const NumericID& other) const
{
return (value > other.value);
}
bool operator<=(const NumericID& other) const
{
return (value <= other.value);
}
bool operator>=(const NumericID& other) const
{
return (value >= other.value);
}
NumericID& operator++()
{
++value;
return *this;
}
NumericID operator++(int)
{
NumericID result = *this;
++value;
return result;
}
NumericID& operator--()
{
--value;
return *this;
}
NumericID operator--(int)
{
NumericID result = *this;
--value;
return result;
}
bool operator!() const
{
return (value == 0);
}
explicit operator bool() const
{
return value != 0;
}
friend std::ostream& operator<< (std::ostream& out, const NumericID& obj)
{
return out << obj.str();
}
friend std::istream& operator>> (std::istream& in, NumericID& obj)
{
in >> obj.value;
return in;
}
protected:
T value;
};
namespace std
{
template<typename T, typename Tag>
struct hash<NumericID<T, Tag>>
{
size_t operator()(const NumericID<T, Tag>& id) const
{
return std::hash<T>()(id.val());
}
};
template<typename T, typename Tag>
class numeric_limits<NumericID<T, Tag>>
{
public:
static constexpr bool is_specialized = true;
static constexpr bool is_signed = std::numeric_limits<T>::is_signed;
static constexpr bool is_integer = std::numeric_limits<T>::is_integer;
static constexpr bool is_exact = std::numeric_limits<T>::is_exact;
static constexpr bool has_infinity = std::numeric_limits<T>::has_infinity;
static constexpr bool has_quiet_NaN = std::numeric_limits<T>::has_quiet_NaN;
static constexpr bool has_signaling_NaN = std::numeric_limits<T>::has_signaling_NaN;
static constexpr bool has_denorm = std::numeric_limits<T>::has_denorm;
static constexpr bool has_denorm_loss = std::numeric_limits<T>::has_denorm_loss;
static constexpr std::float_round_style round_style = std::numeric_limits<T>::round_style;
static constexpr bool is_iec559 = std::numeric_limits<T>::is_iec559;
static constexpr bool is_bounded = std::numeric_limits<T>::is_bounded;
static constexpr bool is_modulo = std::numeric_limits<T>::is_modulo;
static constexpr int digits = std::numeric_limits<T>::digits;
static constexpr int digits10 = std::numeric_limits<T>::digits10;
static constexpr int max_digits10 = std::numeric_limits<T>::max_digits10;
static constexpr int radix = std::numeric_limits<T>::radix;
static constexpr int min_exponent = std::numeric_limits<T>::min_exponent;
static constexpr int min_exponent10 = std::numeric_limits<T>::min_exponent10;
static constexpr int max_exponent = std::numeric_limits<T>::max_exponent;
static constexpr int max_exponent10 = std::numeric_limits<T>::max_exponent10;
static constexpr bool traps = std::numeric_limits<T>::traps;
static constexpr bool tinyness_before = std::numeric_limits<T>::tinyness_before;
static NumericID<T, Tag> min() noexcept { return NumericID<T, Tag>(numeric_limits<T>::min()); }
static NumericID<T, Tag> lowest() noexcept { return NumericID<T, Tag>(numeric_limits<T>::lowest()); }
static NumericID<T, Tag> max() noexcept { return NumericID<T, Tag>(numeric_limits<T>::max()); }
static NumericID<T, Tag> epsilon() noexcept { return NumericID<T, Tag>(numeric_limits<T>::epsilon()); }
static NumericID<T, Tag> round_error() noexcept { return NumericID<T, Tag>(numeric_limits<T>::round_error()); }
static NumericID<T, Tag> infinity() noexcept { return NumericID<T, Tag>(numeric_limits<T>::infinity()); }
static NumericID<T, Tag> quiet_NaN() noexcept { return NumericID<T, Tag>(numeric_limits<T>::quiet_NaN()); }
static NumericID<T, Tag> signaling_NaN() noexcept { return NumericID<T, Tag>(numeric_limits<T>::signaling_NaN()); }
static NumericID<T, Tag> denorm_min() noexcept { return NumericID<T, Tag>(numeric_limits<T>::denorm_min()); }
};
}

View File

@@ -0,0 +1,201 @@
#include <common/toolkit/StorageTk.h>
#include <common/toolkit/NodesTk.h>
#include "AbstractApp.h"
bool AbstractApp::didRunTimeInit = false;
/**
* Note: Will do nothing if pidFile string is empty.
*
* @pidFile will not be accepted if path is not absolute (=> throws exception).
* @return LockFD for locked pid file or invalid if pidFile string was empty.
*/
LockFD AbstractApp::createAndLockPIDFile(const std::string& pidFile)
{
if (pidFile.empty())
return {};
// PID file defined
Path pidPath(pidFile);
if(!pidPath.absolute() ) /* (check to avoid problems after chdir) */
throw InvalidConfigException("Path to PID file must be absolute: " + pidFile);
auto pidFileLockFD = StorageTk::createAndLockPIDFile(pidFile, true);
if (!pidFileLockFD.valid())
throw InvalidConfigException("Unable to create/lock PID file: " + pidFile);
return pidFileLockFD;
}
/**
* Updates a locked pid file, which is typically required after calling daemon().
*
* Note: Will do nothing if pidfileFD is invalid.
*/
void AbstractApp::updateLockedPIDFile(LockFD& pidFileFD)
{
if (!pidFileFD.valid())
return;
if (auto err = pidFileFD.updateWithPID())
throw InvalidConfigException("Unable to update PID file: " + err.message());
}
/**
* @param component the thread that we're waiting for via join(); may be NULL (in which case this
* method returns immediately)
*/
void AbstractApp::waitForComponentTermination(PThread* component)
{
const char* logContext = "App (wait for component termination)";
Logger* logger = Logger::getLogger();
const int timeoutMS = 2000;
if(!component)
return;
bool isTerminated = component->timedjoin(timeoutMS);
if(!isTerminated)
{ // print message to show user which thread is blocking
if(logger)
logger->log(Log_WARNING, logContext,
"Still waiting for this component to stop: " + component->getName() );
component->join();
if(logger)
logger->log(Log_WARNING, logContext, "Component stopped: " + component->getName() );
}
}
/**
* Handle errors from new operator.
*
* This method is intended by the C++ standard to try to free up some memory after allocation
* failed, but our options here are very limited, because we don't know which locks are being held
* when this is called and how much memory the caller actually requested.
*
* @return this function should only return if it freed up memory (otherwise it might be called
* infinitely); as we don't free up any memory here, we exit by throwing a std::bad_alloc
* exeception.
* @throw std::bad_alloc always.
*/
void AbstractApp::handleOutOfMemFromNew()
{
int errCode = errno;
std::cerr << "Failed to allocate memory via \"new\" operator. "
"Will try to log a backtrace and then throw a bad_alloc exception..." << std::endl;
std::cerr << "Last errno value: " << errno << std::endl;
LogContext log(__func__);
log.logErr("Failed to allocate memory via \"new\" operator. "
"Will try to log a backtrace and then throw a bad_alloc exception...");
log.logErr("Last errno value: " + System::getErrString(errCode) );
log.logBacktrace();
throw std::bad_alloc();
}
bool AbstractApp::performBasicInitialRunTimeChecks()
{
bool timeTest = Time::testClock();
if (!timeTest)
return false;
bool conditionTimeTest = Condition::testClockID();
return conditionTimeTest;
}
bool AbstractApp::basicInitializations()
{
bool condAttrRes = Condition::initStaticCondAttr();
return condAttrRes;
}
bool AbstractApp::basicDestructions()
{
bool condAttrRes = Condition::destroyStaticCondAttr();
return condAttrRes;
}
void AbstractApp::logUsableNICs(LogContext* log, NicAddressList& nicList)
{
// list usable network interfaces
std::string nicListStr;
std::string extendedNicListStr;
for(NicAddressListIter nicIter = nicList.begin(); nicIter != nicList.end(); nicIter++)
{
std::string nicTypeStr;
if (nicIter->nicType == NICADDRTYPE_RDMA)
nicTypeStr = "RDMA";
else if (nicIter->nicType == NICADDRTYPE_STANDARD)
nicTypeStr = "TCP";
else
nicTypeStr = "Unknown";
nicListStr += std::string(nicIter->name) + "(" + nicTypeStr + ")" + " ";
extendedNicListStr += "\n+ ";
extendedNicListStr += NetworkInterfaceCard::nicAddrToString(&(*nicIter) ) + " ";
}
nicListStr = std::string("Usable NICs: ") + nicListStr;
extendedNicListStr = std::string("Extended list of usable NICs: ") + extendedNicListStr;
if (log)
{
log->log(Log_WARNING, nicListStr);
log->log(Log_DEBUG, extendedNicListStr);
}
else
{
LOG(GENERAL, WARNING, nicListStr);
LOG(GENERAL, DEBUG, extendedNicListStr);
}
}
void AbstractApp::updateLocalNicListAndRoutes(LogContext* log, NicAddressList& localNicList,
std::vector<AbstractNodeStore*>& nodeStores)
{
bool needRoutes = getCommonConfig()->getConnRestrictOutboundInterfaces();
if (needRoutes)
updateRoutingTable();
std::unique_lock<Mutex> lock(localNicListMutex);
this->localNicList = localNicList;
lock.unlock();
logUsableNICs(log, localNicList);
NicListCapabilities localNicCaps;
NetworkInterfaceCard::supportedCapabilities(&localNicList, &localNicCaps);
for (auto& l : nodeStores)
{
NodesTk::applyLocalNicListToList(localNicList, localNicCaps,
l->referenceAllNodes());
}
}
bool AbstractApp::initNoDefaultRouteList(NetVector* outNets)
{
std::string connNoDefaultRoute = getCommonConfig()->getConnNoDefaultRoute();
StringVector cidrs;
StringTk::explodeEx(connNoDefaultRoute, ',', true, &cidrs);
for (auto& c : cidrs)
{
IPv4Network net;
if (!IPv4Network::parse(c, net))
return false;
outNets->push_back(net);
}
return true;
}

View File

@@ -0,0 +1,131 @@
#pragma once
#include <common/app/log/Logger.h>
#include <common/app/config/ICommonConfig.h>
#include <common/net/message/AbstractNetMessageFactory.h>
#include <common/threading/PThread.h>
#include <common/toolkit/BitStore.h>
#include <common/toolkit/NetFilter.h>
#include <common/toolkit/LockFD.h>
#include <common/Common.h>
#include <common/net/sock/RoutingTable.h>
#include <mutex>
#include <mutex>
class StreamListenerV2; // forward declaration
class LogContext;
class AbstractNodeStore;
class AbstractApp : public PThread
{
public:
virtual void stopComponents() = 0;
virtual void handleComponentException(std::exception& e) = 0;
virtual void handleNetworkInterfaceFailure(const std::string& devname) = 0;
LockFD createAndLockPIDFile(const std::string& pidFile);
void updateLockedPIDFile(LockFD& pidFileFD);
void waitForComponentTermination(PThread* component);
void logUsableNICs(LogContext* log, NicAddressList& nicList);
static void handleOutOfMemFromNew();
virtual const ICommonConfig* getCommonConfig() const = 0;
virtual const NetFilter* getNetFilter() const = 0;
virtual const NetFilter* getTcpOnlyFilter() const = 0;
virtual const AbstractNetMessageFactory* getNetMessageFactory() const = 0;
static bool didRunTimeInit;
/**
* To be overridden by Apps that support multiple stream listeners.
*/
virtual StreamListenerV2* getStreamListenerByFD(int fd)
{
return NULL;
}
NicAddressList getLocalNicList()
{
const std::lock_guard<Mutex> lock(localNicListMutex);
return localNicList;
}
protected:
NicAddressList localNicList; // intersection set of dicsovered NICs and allowedInterfaces
Mutex localNicListMutex;
std::shared_ptr<NetVector> noDefaultRouteNets;
AbstractApp() : PThread("Main", this),
noDefaultRouteNets(nullptr)
{
if (!didRunTimeInit)
{
std::cerr << "Bug: Runtime variables have not been initialized" << std::endl;
throw InvalidConfigException("Bug: Runtime variables have not been initialized");
}
std::set_new_handler(handleOutOfMemFromNew);
}
virtual ~AbstractApp()
{
basicDestructions();
}
void updateLocalNicListAndRoutes(LogContext* log, NicAddressList& nicList,
std::vector<AbstractNodeStore*>& nodeStores);
bool initNoDefaultRouteList(NetVector* outNets);
private:
static bool basicInitializations();
bool basicDestructions();
static bool performBasicInitialRunTimeChecks();
RoutingTableFactory routingTableFactory;
// inliners
public:
/**
* Note: This MUST be called before creating a new App
*/
static bool runTimeInitsAndChecks()
{
bool runTimeChecks = performBasicInitialRunTimeChecks();
if (!runTimeChecks)
return false;
bool initRes = basicInitializations();
if (!initRes)
return false;
didRunTimeInit = true;
return true;
}
void initRoutingTable()
{
routingTableFactory.init();
}
bool updateRoutingTable()
{
return routingTableFactory.load();
}
RoutingTable getRoutingTable()
{
return routingTableFactory.create(noDefaultRouteNets);
}
};

View File

@@ -0,0 +1,514 @@
#include <common/toolkit/StringTk.h>
#include <common/toolkit/StorageTk.h>
#include <common/app/log/LogContext.h>
#include <common/app/log/Logger.h>
#include "AbstractConfig.h"
#include "common/toolkit/HashTk.h"
#define ABSTRACTCONF_AUTHFILE_READSIZE 1024 // max amount of data that we read from auth file
#define ABSTRACTCONF_AUTHFILE_MINSIZE 4 // at least 2, because we compute two 32bit hashes
AbstractConfig::AbstractConfig(int argc, char** argv) :
argc(argc), argv(argv)
{
// initConfig(..) must be called by derived classes
// because of the virtual init method loadDefaults()
}
/**
* Note: This must not be called from the AbstractConfig (or base class) constructor, because it
* calls virtual methods of the sub classes.
*
* @param enableException: true to throw an exception when an unknown config element is found,
* false to ignore it.
* @param addDashes true to prepend dashes to defaults and config file keys (used e.g. by fhgfs-ctl)
*/
void AbstractConfig::initConfig(int argc, char** argv, bool enableException, bool addDashes)
{
// load and apply args to see whether we have a cfgFile
loadDefaults(addDashes);
loadFromArgs(argc, argv);
applyConfigMap(enableException, addDashes);
if(this->cfgFile.length() )
{ // there is a config file specified
// start over again and include the config file this time
this->configMap.clear();
const std::string tmpCfgFile = this->cfgFile; /* need tmp variable here to make sure we don't
override the value when we use call loadDefaults() again below, because subclasses have
direct access to this->cfgFile */
loadDefaults(addDashes);
loadFromFile(tmpCfgFile.c_str(), addDashes);
loadFromArgs(argc, argv);
applyConfigMap(enableException, addDashes);
}
initImplicitVals();
}
/**
* Sets the default values for each configurable in the configMap.
*
* @param addDashes true to prepend "--" to all config keys.
*/
void AbstractConfig::loadDefaults(bool addDashes)
{
configMapRedefine("cfgFile", "", addDashes);
configMapRedefine("logType", "logfile", addDashes);
configMapRedefine("logLevel", "3", addDashes);
configMapRedefine("logNoDate", "true", addDashes);
configMapRedefine("logStdFile", "", addDashes);
configMapRedefine("logNumLines", "50000", addDashes);
configMapRedefine("logNumRotatedFiles", "2", addDashes);
configMapRedefine("connPortShift", "0", addDashes);
// To be able to merge these with the legacy settings later, we set them to -1 here. Otherwise it
// is impossible to detect if they have actually been set or just loaded the default.
// The actual default values are applied during the post processing in applyConfigMap.
configMapRedefine("connClientPort", "-1", addDashes); // 8004
configMapRedefine("connStoragePort", "-1", addDashes); // 8003
configMapRedefine("connMetaPort", "-1", addDashes); // 8005
configMapRedefine("connMonPort", "-1", addDashes); // 8007
configMapRedefine("connMgmtdPort", "-1", addDashes); // 8008
configMapRedefine("connUseRDMA", "true", addDashes);
configMapRedefine("connBacklogTCP", "64", addDashes);
configMapRedefine("connMaxInternodeNum", "6", addDashes);
configMapRedefine("connFallbackExpirationSecs", "900", addDashes);
configMapRedefine("connTCPRcvBufSize", "0", addDashes);
configMapRedefine("connUDPRcvBufSize", "0", addDashes);
configMapRedefine("connRDMABufSize", "8192", addDashes);
configMapRedefine("connRDMABufNum", "70", addDashes);
configMapRedefine("connRDMATypeOfService", "0", addDashes);
configMapRedefine("connNetFilterFile", "", addDashes);
configMapRedefine("connAuthFile", "", addDashes);
configMapRedefine("connDisableAuthentication", "false", addDashes);
configMapRedefine("connTcpOnlyFilterFile", "", addDashes);
configMapRedefine("connRestrictOutboundInterfaces", "false", addDashes);
configMapRedefine("connNoDefaultRoute", "0.0.0.0/0", addDashes);
/* connMessagingTimeouts: default to zero, indicating that constants
* specified in Common.h are used.
*/
configMapRedefine("connMessagingTimeouts", "0,0,0", addDashes);
// connRDMATimeouts: zero values are interpreted as the defaults specified in IBVSocket.cpp
configMapRedefine("connRDMATimeouts", "0,0,0", addDashes);
configMapRedefine("sysMgmtdHost", "", addDashes);
configMapRedefine("sysUpdateTargetStatesSecs", "0", addDashes);
}
/**
* @param enableException: true to throw an exception when an unknown config element is found,
* false to ignore it.
* @param addDashes true to prepend "--" to tested config keys for matching.
*/
void AbstractConfig::applyConfigMap(bool enableException, bool addDashes)
{
// Deprecated separate port settings. These are post processed below and merged into the new
// combined settings.
int connClientPortUDP = -1;
int connMgmtdPortUDP = -1;
int connMetaPortUDP = -1;
int connStoragePortUDP = -1;
int connMonPortUDP = -1;
int connClientPortTCP = -1;
int connMgmtdPortTCP = -1;
int connMetaPortTCP = -1;
int connStoragePortTCP = -1;
int connMonPortTCP = -1;
for (StringMapIter iter = configMap.begin(); iter != configMap.end();)
{
bool unknownElement = false;
if (testConfigMapKeyMatch(iter, "cfgFile", addDashes))
cfgFile = iter->second;
else if (testConfigMapKeyMatch(iter, "logLevel", addDashes))
logLevel = StringTk::strToInt(iter->second);
else if (testConfigMapKeyMatch(iter, "logNoDate", addDashes))
logNoDate = StringTk::strToBool(iter->second);
else if (testConfigMapKeyMatch(iter, "logStdFile", addDashes))
logStdFile = iter->second;
else if (testConfigMapKeyMatch(iter, "logNumLines", addDashes))
logNumLines = StringTk::strToInt(iter->second);
else if (testConfigMapKeyMatch(iter, "logNumRotatedFiles", addDashes))
logNumRotatedFiles = StringTk::strToInt(iter->second);
else if (testConfigMapKeyMatch(iter, "connPortShift", addDashes))
connPortShift = StringTk::strToInt(iter->second);
else if (testConfigMapKeyMatch(iter, "connClientPort", addDashes))
assignKeyIfNotZero(iter, connClientPort, enableException);
else if (testConfigMapKeyMatch(iter, "connStoragePort", addDashes))
assignKeyIfNotZero(iter, connStoragePort, enableException);
else if (testConfigMapKeyMatch(iter, "connMetaPort", addDashes))
assignKeyIfNotZero(iter, connMetaPort, enableException);
else if (testConfigMapKeyMatch(iter, "connMonPort", addDashes))
assignKeyIfNotZero(iter, connMonPort, enableException);
else if (testConfigMapKeyMatch(iter, "connMgmtdPort", addDashes))
assignKeyIfNotZero(iter, connMgmtdPort, enableException);
else if (testConfigMapKeyMatch(iter, "connClientPortUDP", addDashes))
assignKeyIfNotZero(iter, connClientPortUDP, enableException);
else if (testConfigMapKeyMatch(iter, "connStoragePortUDP", addDashes))
assignKeyIfNotZero(iter, connStoragePortUDP, enableException);
else if (testConfigMapKeyMatch(iter, "connMetaPortUDP", addDashes))
assignKeyIfNotZero(iter, connMetaPortUDP, enableException);
else if (testConfigMapKeyMatch(iter, "connMonPortUDP", addDashes))
assignKeyIfNotZero(iter, connMonPortUDP, enableException);
else if (testConfigMapKeyMatch(iter, "connMgmtdPortUDP", addDashes))
assignKeyIfNotZero(iter, connMgmtdPortUDP, enableException);
else if (testConfigMapKeyMatch(iter, "connStoragePortTCP", addDashes))
assignKeyIfNotZero(iter, connStoragePortTCP, enableException);
else if (testConfigMapKeyMatch(iter, "connMetaPortTCP", addDashes))
assignKeyIfNotZero(iter, connMetaPortTCP, enableException);
else if (testConfigMapKeyMatch(iter, "connMgmtdPortTCP", addDashes))
assignKeyIfNotZero(iter, connMgmtdPortTCP, enableException);
else if (testConfigMapKeyMatch(iter, "connUseRDMA", addDashes))
connUseRDMA = StringTk::strToBool(iter->second);
else if (testConfigMapKeyMatch(iter, "connBacklogTCP", addDashes))
connBacklogTCP = StringTk::strToUInt(iter->second);
else if (testConfigMapKeyMatch(iter, "connMaxInternodeNum", addDashes))
connMaxInternodeNum = StringTk::strToUInt(iter->second);
else if (testConfigMapKeyMatch(iter, "connNonPrimaryExpiration", addDashes))
{
// superseded by connFallbackExpirationSecs, ignored here for config file compatibility
}
else if (testConfigMapKeyMatch(iter, "connFallbackExpirationSecs", addDashes))
connFallbackExpirationSecs = StringTk::strToUInt(iter->second);
else if (testConfigMapKeyMatch(iter, "connTCPRcvBufSize", addDashes))
connTCPRcvBufSize = StringTk::strToInt(iter->second);
else if (testConfigMapKeyMatch(iter, "connUDPRcvBufSize", addDashes))
connUDPRcvBufSize = StringTk::strToInt(iter->second);
else if (testConfigMapKeyMatch(iter, "connRDMABufSize", addDashes))
connRDMABufSize = StringTk::strToUInt(iter->second);
else if (testConfigMapKeyMatch(iter, "connRDMABufNum", addDashes))
connRDMABufNum = StringTk::strToUInt(iter->second);
else if (testConfigMapKeyMatch(iter, "connRDMATypeOfService", addDashes))
connRDMATypeOfService = (uint8_t)StringTk::strToUInt(iter->second);
else if (testConfigMapKeyMatch(iter, "connNetFilterFile", addDashes))
connNetFilterFile = iter->second;
else if (testConfigMapKeyMatch(iter, "connAuthFile", addDashes))
connAuthFile = iter->second;
else if (testConfigMapKeyMatch(iter, "connDisableAuthentication", addDashes))
connDisableAuthentication = StringTk::strToBool(iter->second);
else if (testConfigMapKeyMatch(iter, "connTcpOnlyFilterFile", addDashes))
connTcpOnlyFilterFile = iter->second;
else if (testConfigMapKeyMatch(iter, "connRestrictOutboundInterfaces", addDashes))
connRestrictOutboundInterfaces = StringTk::strToBool(iter->second);
else if (testConfigMapKeyMatch(iter, "connNoDefaultRoute", addDashes))
connNoDefaultRoute = iter->second;
else if (testConfigMapKeyMatch(iter, "connMessagingTimeouts", addDashes))
{
const size_t cfgValCount = 3; // count value in config file in order of long, medium and short
std::list<std::string> split;
StringList::iterator timeoutIter;
StringTk::explode(iter->second, ',', &split);
if (split.size() == cfgValCount)
{
timeoutIter = split.begin();
connMsgLongTimeout = StringTk::strToInt(*timeoutIter) > 0 ?
StringTk::strToInt(*timeoutIter) : CONN_LONG_TIMEOUT;
timeoutIter++;
connMsgMediumTimeout = StringTk::strToInt(*timeoutIter) > 0 ?
StringTk::strToInt(*timeoutIter) : CONN_MEDIUM_TIMEOUT;
timeoutIter++;
connMsgShortTimeout = StringTk::strToInt(*timeoutIter) > 0 ?
StringTk::strToInt(*timeoutIter) : CONN_SHORT_TIMEOUT;
}
else
throw InvalidConfigException("The config argument '" + iter->first + "' is invalid");
}
else if (testConfigMapKeyMatch(iter, "connRDMATimeouts", addDashes))
{
const size_t cfgValCount = 3;
const size_t cfgUtilValCount = 5;
std::list<std::string> split;
StringTk::explode(iter->second, ',', &split);
// YUCK. beegfs-ctl and beegfs-fsck use beegfs-client.conf. That file defines
// connRDMATimeouts as 5 comma-separated values, but user space only has 3
// values. The utils are supposed to ignore the configuration. cfgUtilValCount
// is a hack to prevent the configuration from causing a runtime error.
if (split.size() != cfgUtilValCount)
{
if (split.size() == cfgValCount)
{
StringList::iterator timeoutIter = split.begin();
connRDMATimeoutConnect = StringTk::strToInt(*timeoutIter++);
connRDMATimeoutFlowSend = StringTk::strToInt(*timeoutIter++);
connRDMATimeoutPoll = StringTk::strToInt(*timeoutIter);
}
else
{
throw InvalidConfigException("The config argument '" + iter->first + "' is invalid");
}
}
}
else if (testConfigMapKeyMatch(iter, "sysMgmtdHost", addDashes))
sysMgmtdHost = iter->second;
else if (testConfigMapKeyMatch(iter, "sysUpdateTargetStatesSecs", addDashes))
sysUpdateTargetStatesSecs = StringTk::strToUInt(iter->second);
else
{
// unknown element occurred
unknownElement = true;
if (enableException)
{
throw InvalidConfigException("The config argument '" + iter->first + "' is invalid");
}
}
if (unknownElement)
{
// just skip the unknown element
iter++;
}
else
{
// remove this element from the map
iter = eraseFromConfigMap(iter);
}
}
auto processPortSettings = [&](const std::string& name, int& setting, const int& tcp, const int& udp, const int def) {
if(setting == -1) {
if(tcp != -1 && udp != -1 && tcp != udp) {
throw InvalidConfigException("Deprecated config arguments '" + name + "UDP' and \
'" + name + "TCP' set to different values, which is no longer allowed. Please use the new '" + name + "' \
setting instead.");
}
// Set the new setting using the old values
if(tcp != -1) {
setting = tcp;
// TODO Doesn't work as the logger isn't initialized yet: https://github.com/ThinkParQ/beegfs-core/issues/4033
LOG(GENERAL, WARNING, "Using deprecated config argument '" + name + "TCP'");
} else if(udp != -1) {
setting = udp;
// TODO Doesn't work as the logger isn't initialized yet: https://github.com/ThinkParQ/beegfs-core/issues/4033
LOG(GENERAL, WARNING, "Using deprecated config argument '" + name + "UDP'");
} else {
setting = def;
}
} else {
if(tcp != -1 || udp != -1) {
throw InvalidConfigException("Deprecated config arguments '" + name + "UDP/TCP' set along with the new \
'" + name + "' setting. Please use only the new setting.");
}
}
};
processPortSettings("connClientPort", this->connClientPort, connClientPortTCP, connClientPortUDP, 8004);
processPortSettings("connStoragePort", this->connStoragePort, connStoragePortTCP, connStoragePortUDP, 8003);
processPortSettings("connMetaPort", this->connMetaPort, connMetaPortTCP, connMetaPortUDP, 8005);
processPortSettings("connMonPort", this->connMonPort, connMonPortTCP, connMonPortUDP, 8007);
processPortSettings("connMgmtdPort", this->connMgmtdPort, connMgmtdPortTCP, connMgmtdPortUDP, 8008);
}
void AbstractConfig::initImplicitVals()
{
// nothing to be done here (just a dummy so that derived classes don't have to impl this)
}
/**
* Initialize interfaces list from interfaces file if the list is currently empty and the filename
* is not empty.
*
* @param inoutConnInterfacesList will be initialized from file (comma-separated) if it was empty
*
* @throw InvalidConfigException if interfaces filename and interface list are both not empty
*/
void AbstractConfig::initInterfacesList(const std::string& connInterfacesFile,
std::string& inoutConnInterfacesList)
{
// make sure not both (list and file) were specified
if(!inoutConnInterfacesList.empty() && !connInterfacesFile.empty() )
{
throw InvalidConfigException(
"connInterfacesFile and connInterfacesList cannot be used together");
}
if(!inoutConnInterfacesList.empty() )
return; // interfaces already given as list => nothing to do
if(connInterfacesFile.empty() )
return; // no interfaces file given => nothing to do
// load interfaces from file...
StringList loadedInterfacesList;
loadStringListFile(connInterfacesFile.c_str(), loadedInterfacesList);
inoutConnInterfacesList = StringTk::implode(',', loadedInterfacesList, true);
}
/**
* Generate connection authentication hash based on contents of given authentication file.
*
* @param outConnAuthHash will be set to 0 if file is not defined
*
* @throw InvalidConfigException if connAuthFile is defined, but cannot be read.
*/
void AbstractConfig::initConnAuthHash(const std::string& connAuthFile, uint64_t* outConnAuthHash)
{
if (connDisableAuthentication) {
*outConnAuthHash = 0;
return; // connAuthFile explicitly disabled => no hash to be generated
}
// Connection authentication not explicitly disabled, so bail if connAuthFile is not configured
if(connAuthFile.empty())
throw ConnAuthFileException("No connAuthFile configured. Using BeeGFS without connection authentication is considered insecure and is not recommended. If you really want or need to run BeeGFS without connection authentication, please set connDisableAuthentication to true.");
// open file...
/* note: we don't reuse something like loadStringListFile() here, because:
1) the auth file might not contain a string, but can be any binary data (including zeros).
2) we want to react on EACCES. */
int fd = open(connAuthFile.c_str(), O_RDONLY);
int errCode = errno;
if( (fd == -1) && (errCode == EACCES) )
{ // current effective user/group ID not allowed to read file => try it with saved IDs
unsigned previousUID;
unsigned previousGID;
// switch to saved IDs
System::elevateUserAndGroupFsID(&previousUID, &previousGID);
fd = open(connAuthFile.c_str(), O_RDONLY);
errCode = errno; // because ID dropping might change errno
// restore previous IDs
System::setFsIDs(previousUID, previousGID, &previousUID, &previousGID);
}
if(fd == -1)
{
throw ConnAuthFileException("Unable to open auth file: " + connAuthFile + " "
"(SysErr: " + System::getErrString(errCode) + ")");
}
// load file contents...
unsigned char buf[ABSTRACTCONF_AUTHFILE_READSIZE];
const ssize_t readRes = read(fd, buf, ABSTRACTCONF_AUTHFILE_READSIZE);
errCode = errno; // because close() might change errno
close(fd);
if(readRes < 0)
{
throw InvalidConfigException("Unable to read auth file: " + connAuthFile + " "
"(SysErr: " + System::getErrString(errCode) + ")");
}
// empty authFile is probably unintended, so treat it as error
if(!readRes || (readRes < ABSTRACTCONF_AUTHFILE_MINSIZE) )
throw InvalidConfigException("Auth file is empty: " + connAuthFile);
// hash file contents
*outConnAuthHash = HashTk::authHash(buf, readRes);
}
/**
* Sets the value of connTCPRcvBufSize and connUDPRcvBufSize according to the configuration.
* 0 indicates legacy behavior that uses RDMA bufsizes. Otherwise leave the values as
* configured.
*/
void AbstractConfig::initSocketBufferSizes()
{
int legacy = connRDMABufSize * connRDMABufNum;
if (connTCPRcvBufSize == 0)
connTCPRcvBufSize = legacy;
if (connUDPRcvBufSize == 0)
connUDPRcvBufSize = legacy;
}
/**
* Removes an element from the config map and returns an iterator that is positioned right
* after the removed element
*/
StringMapIter AbstractConfig::eraseFromConfigMap(StringMapIter iter)
{
StringMapIter nextIter(iter);
nextIter++; // save position after the erase element
configMap.erase(iter);
return nextIter;
}
/**
* @param addDashes true to prepend "--" to every config key.
*/
void AbstractConfig::loadFromFile(const char* filename, bool addDashes)
{
if(!addDashes)
{ // no dashes needed => load directly into configMap
MapTk::loadStringMapFromFile(filename, &configMap);
return;
}
/* we need to add dashes to keys => use temporary map with real keys and then copy to actual
config map with prepended dashes. */
StringMap tmpMap;
MapTk::loadStringMapFromFile(filename, &tmpMap);
for(StringMapCIter iter = tmpMap.begin(); iter != tmpMap.end(); iter++)
configMapRedefine(iter->first, iter->second, addDashes);
}
/**
* Note: No addDashes param here, because the user should specify dashes himself on the command line
* if they are needed.
*/
void AbstractConfig::loadFromArgs(int argc, char** argv)
{
for(int i=1; i < argc; i++)
MapTk::addLineToStringMap(argv[i], &configMap);
}
/**
* @warning It might exit the proccess if it finds an incorrect value
*/
void AbstractConfig::assignKeyIfNotZero(const StringMapIter& it, int& intVal, bool enableException)
{
const int tempVal = StringTk::strToInt(it->second);
if (tempVal == 0) {
if (enableException) {
throw InvalidConfigException("Invalid or unset configuration variable: " + (it->first));
}
return;
}
intVal = tempVal;
}

View File

@@ -0,0 +1,107 @@
#pragma once
#include <common/Common.h>
#include <common/toolkit/MapTk.h>
#include "InvalidConfigException.h"
#include "ConnAuthFileException.h"
#include "ICommonConfig.h"
class AbstractConfig : public ICommonConfig
{
public:
virtual ~AbstractConfig() {}
protected:
// internals
StringMap configMap;
int argc;
char** argv;
void addLineToConfigMap(std::string line);
void loadFromArgs(int argc, char** argv);
// configurables
std::string cfgFile;
AbstractConfig(int argc, char** argv);
void initConfig(int argc, char** argv, bool enableException, bool addDashes=false);
StringMapIter eraseFromConfigMap(StringMapIter iter);
virtual void loadDefaults(bool addDashes=false);
void loadFromFile(const char* filename, bool addDashes=false);
virtual void applyConfigMap(bool enableException, bool addDashes=false);
virtual void initImplicitVals();
void initInterfacesList(const std::string& connInterfacesFile,
std::string& inoutConnInterfacesList);
void initConnAuthHash(const std::string& connAuthFile, uint64_t* outConnAuthHash);
void initSocketBufferSizes();
// inliners
/**
* @param addDashes true to prepend "--" to the keyStr.
*/
void configMapRedefine(std::string keyStr, std::string valueStr, bool addDashes=false)
{
std::string keyStrInternal;
if(addDashes)
keyStrInternal = "--" + keyStr;
else
keyStrInternal = keyStr;
MapTk::stringMapRedefine(keyStrInternal, valueStr, &configMap);
}
/**
* Note: Read the addDashesToTestKey comment on case-sensitivity.
*
* @param addDashesToTestKey true to prepend "--" to testKey before testing for match; if this
* is specified, the check will also be case-insensitive (otherwise it is case-sensitive).
* @return true if iter->first equals testKey.
*/
bool testConfigMapKeyMatch(const StringMapCIter& iter, const std::string& testKey,
bool addDashesToTestKey) const
{
if(addDashesToTestKey)
{
std::string testKeyDashed = "--" + testKey;
return (!strcasecmp(iter->first.c_str(), testKeyDashed.c_str() ) );
}
else
return (iter->first == testKey);
}
// getters & setters
const StringMap* getConfigMap() const
{
return &configMap;
}
int getArgc() const
{
return argc;
}
char** getArgv() const
{
return argv;
}
static void assignKeyIfNotZero(const StringMapIter&, int& intVal, bool enableException = true);
public:
std::string getCfgFile() const
{
return cfgFile;
}
};

View File

@@ -0,0 +1,8 @@
#pragma once
#include <common/toolkit/NamedException.h>
#include <common/Common.h>
DECLARE_NAMEDEXCEPTION(ConnAuthFileException, "ConnAuthFileException")

View File

@@ -0,0 +1,36 @@
#include <common/toolkit/StringTk.h>
#include <common/toolkit/StorageTk.h>
#include "ICommonConfig.h"
/**
* Loads a file into a string list (line by line).
*
* Loaded strings are trimmed before they are added to the list. Empty lines and lines starting
* with STORAGETK_FILE_COMMENT_CHAR are not added to the list.
*/
void ICommonConfig::loadStringListFile(const char* filename, StringList& outList)
{
std::ifstream fis(filename);
if(!fis.is_open() || fis.fail() )
{
throw InvalidConfigException(
std::string("Failed to open file: ") + filename);
}
while(!fis.eof() && !fis.fail() )
{
std::string line;
std::getline(fis, line);
std::string trimmedLine = StringTk::trim(line);
if(trimmedLine.length() && (trimmedLine[0] != STORAGETK_FILE_COMMENT_CHAR) )
outList.push_back(trimmedLine);
}
fis.close();
}

View File

@@ -0,0 +1,247 @@
#pragma once
#include <common/Common.h>
#include "InvalidConfigException.h"
class ICommonConfig
{
public:
virtual ~ICommonConfig() {}
static void loadStringListFile(const char* filename, StringList& outList);
protected:
ICommonConfig() {}
LogType logType;
int logLevel;
bool logNoDate;
std::string logStdFile;
unsigned logNumLines;
unsigned logNumRotatedFiles;
int connPortShift; // shifts all UDP and TCP ports
int connClientPort;
int connStoragePort;
int connMetaPort;
int connMonPort;
int connMgmtdPort;
bool connUseRDMA;
unsigned connBacklogTCP;
unsigned connMaxInternodeNum;
unsigned connFallbackExpirationSecs;
int connTCPRcvBufSize;
int connUDPRcvBufSize;
unsigned connRDMABufSize;
unsigned connRDMABufNum;
uint8_t connRDMATypeOfService;
std::string connNetFilterFile; // for allowed IPs (empty means "allow all")
std::string connAuthFile;
bool connDisableAuthentication;
uint64_t connAuthHash; // implicitly set based on hash of connAuthFile contents
std::string connTcpOnlyFilterFile; // for IPs that only allow plain TCP (no RDMA etc)
bool connRestrictOutboundInterfaces;
std::string connNoDefaultRoute;
int connMsgLongTimeout;
int connMsgMediumTimeout;
int connMsgShortTimeout; // connection (response) timeouts in ms
// note: be careful here, because servers not
// responding for >30secs under high load is nothing
// unusual, so never use connMsgShortTimeout for
// IO-related operations.
int connRDMATimeoutConnect;
int connRDMATimeoutFlowSend;
int connRDMATimeoutPoll;
std::string sysMgmtdHost;
unsigned sysUpdateTargetStatesSecs;
int connectionRejectionRate;
int connectionRejectionCount;
public:
// getters & setters
LogType getLogType() const
{
return logType;
}
int getLogLevel() const
{
return logLevel;
}
bool getLogNoDate() const
{
return logNoDate;
}
const std::string& getLogStdFile() const
{
return logStdFile;
}
unsigned getLogNumLines() const
{
return logNumLines;
}
unsigned getLogNumRotatedFiles() const
{
return logNumRotatedFiles;
}
int getConnClientPort() const
{
return connClientPort ? (connClientPort + connPortShift) : 0;
}
int getConnStoragePort() const
{
return connStoragePort ? (connStoragePort + connPortShift) : 0;
}
int getConnMetaPort() const
{
return connMetaPort ? (connMetaPort + connPortShift) : 0;
}
int getConnMonPort() const
{
return connMonPort ? (connMonPort + connPortShift) : 0;
}
int getConnMgmtdPort() const
{
return connMgmtdPort ? (connMgmtdPort + connPortShift) : 0;
}
bool getConnUseRDMA() const
{
return connUseRDMA;
}
unsigned getConnBacklogTCP() const
{
return connBacklogTCP;
}
unsigned getConnMaxInternodeNum() const
{
return connMaxInternodeNum;
}
int getConnFallbackExpirationSecs() const
{
return connFallbackExpirationSecs;
}
int getConnTCPRcvBufSize() const
{
return connTCPRcvBufSize;
}
int getConnUDPRcvBufSize() const
{
return connUDPRcvBufSize;
}
unsigned getConnRDMABufSize() const
{
return connRDMABufSize;
}
unsigned getConnRDMABufNum() const
{
return connRDMABufNum;
}
uint8_t getConnRDMATypeOfService() const
{
return connRDMATypeOfService;
}
const std::string& getConnNetFilterFile() const
{
return connNetFilterFile;
}
const std::string& getConnAuthFile() const
{
return connAuthFile;
}
uint64_t getConnAuthHash() const
{
return connAuthHash;
}
const std::string& getConnTcpOnlyFilterFile() const
{
return connTcpOnlyFilterFile;
}
bool getConnRestrictOutboundInterfaces() const
{
return connRestrictOutboundInterfaces;
}
std::string getConnNoDefaultRoute() const
{
return connNoDefaultRoute;
}
int getConnMsgLongTimeout() const
{
return connMsgLongTimeout;
}
int getConnMsgMediumTimeout() const
{
return connMsgMediumTimeout;
}
int getConnMsgShortTimeout() const
{
return connMsgShortTimeout;
}
int getConnRDMATimeoutConnect() const
{
return connRDMATimeoutConnect;
}
int getConnRDMATimeoutFlowSend() const
{
return connRDMATimeoutFlowSend;
}
int getConnRDMATimeoutPoll() const
{
return connRDMATimeoutPoll;
}
const std::string& getSysMgmtdHost() const
{
return sysMgmtdHost;
}
unsigned getSysUpdateTargetStatesSecs() const
{
return sysUpdateTargetStatesSecs;
}
unsigned getConnectionRejectionRate() const
{
return connectionRejectionRate;
}
void setConnectionRejectionRate(unsigned rate)
{
connectionRejectionRate = rate;
connectionRejectionCount = 0;
}
};

View File

@@ -0,0 +1,8 @@
#pragma once
#include <common/toolkit/NamedException.h>
#include <common/Common.h>
DECLARE_NAMEDEXCEPTION(InvalidConfigException, "InvalidConfigException")

View File

@@ -0,0 +1,119 @@
#pragma once
#include <common/app/log/Logger.h>
#include <common/app/AbstractApp.h>
#include <common/threading/PThread.h>
#include <common/Common.h>
#include <execinfo.h>
#define LOGCONTEXT_BACKTRACE_ARRAY_SIZE 32
#ifdef LOG_DEBUG_MESSAGES
#define LOG_DEBUG_CONTEXT(logContext, level, msgStr) \
do { (logContext).log(level, msgStr); } while(0)
#define LOG_DEBUG_BACKTRACE() \
do { LogContext(__func__).logBacktrace(); } while(0)
#else
#define LOG_DEBUG_CONTEXT(logContext, level, msgStr) \
do { /* nothing */ } while(0)
#define LOG_DEBUG_BACKTRACE() \
do { /* nothing */ } while(0)
#endif // LOG_DEBUG_MESSAGES
class LogContext
{
public:
LogContext(const std::string& contextStr="<undefined>") : contextStr(contextStr)
{
this->logger = Logger::getLogger();
}
void log(LogTopic logTopic, int level, const char* msg)
{
if(unlikely(!logger) )
return;
logger->log(logTopic, level, contextStr.c_str(), msg);
}
void log(LogTopic logTopic, int level, const std::string msg)
{
log(logTopic, level, msg.c_str() );
}
void log(int level, const char* msg)
{
log(LogTopic_GENERAL, level, msg);
}
void log(int level, const std::string& msg)
{
log(level, msg.c_str());
}
void logErr(const char* msg)
{
if(unlikely(!logger) )
return;
logger->log(Log_ERR, contextStr.c_str(), msg);
}
void logErr(const std::string& msg)
{
logErr(msg.c_str() );
}
void logBacktrace()
{
int backtraceLength = 0;
char** backtraceSymbols = NULL;
void* backtraceArray[LOGCONTEXT_BACKTRACE_ARRAY_SIZE];
backtraceLength = backtrace(backtraceArray, LOGCONTEXT_BACKTRACE_ARRAY_SIZE);
// note: symbols are malloc'ed and need to be freed later
backtraceSymbols = backtrace_symbols(backtraceArray, backtraceLength);
if(unlikely(!logger) )
return;
logger->logBacktrace(contextStr.c_str(), backtraceLength, backtraceSymbols);
SAFE_FREE(backtraceSymbols);
}
protected:
LogContext(Logger* logger, const std::string& context) : contextStr(context)
{
// for derived classes that have a different way of finding the logger
// (other than via the thread-local storage)
// ...and remember to init the context also ;)
this->logger = logger;
}
// getters & setters
void setLogger(Logger* logger)
{
this->logger = logger;
}
private:
const std::string contextStr;
Logger* logger;
};

View File

@@ -0,0 +1,254 @@
#include <common/toolkit/StringTk.h>
#include <common/threading/PThread.h>
#include <common/toolkit/TimeAbs.h>
#include "Logger.h"
#include <ctime>
#undef LOG_DEBUG
#include <syslog.h>
#define LOGGER_ROTATED_FILE_SUFFIX ".old-"
#define LOGGER_TIMESTR_SIZE 32
std::unique_ptr<Logger> Logger::logger;
// Note: Keep in sync with enum LogTopic
const char* const Logger::LogTopics[LogTopic_INVALID] =
{
"general",
"states",
"mirroring",
"workqueues",
"storage-pools",
"capacity",
"communication",
"quota",
"sessions",
"event-logger",
"database",
"socklib",
};
static const int syslogLevelMapping[Log_LEVELMAX] = { LOG_ERR, LOG_CRIT, LOG_WARNING,
LOG_NOTICE, LOG_DEBUG, LOG_DEBUG };
Logger::Logger(int defaultLevel, LogType cfgLogType, bool noDate, const std::string& stdFile,
unsigned linesPerFile, unsigned rotatedFiles):
logType(cfgLogType), logLevels(LogTopic_INVALID, defaultLevel),
logNoDate(noDate),logStdFile(stdFile), logNumLines(linesPerFile),logNumRotatedFiles(rotatedFiles)
{
this->stdFile = stdout;
this->errFile = stderr;
this->timeFormat = logNoDate ? "%X" : "%b%d %X"; // set time format
this->rotatedFileSuffix = LOGGER_ROTATED_FILE_SUFFIX;
pthread_rwlockattr_t attr;
pthread_rwlockattr_init(&attr);
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_READER_NP);
pthread_rwlock_init(&this->rwLock, &attr);
// prepare file handles and rotate
prepareLogFiles();
}
Logger::~Logger()
{
// close files
if(this->stdFile != stdout)
fclose(this->stdFile);
pthread_rwlock_destroy(&this->rwLock);
}
/**
* Prints a message to the standard log.
* Note: Doesn't lock the outputLock
*
* @param level the level of relevance (should be greater than 0)
* @param msg the actual log message
*/
void Logger::logGrantedUnlocked(int level, const char* threadName, const char* context,
int line, const char* msg)
{
char timeStr[LOGGER_TIMESTR_SIZE];
TimeAbs nowTime;
getTimeStr(nowTime.getTimeS(), timeStr, LOGGER_TIMESTR_SIZE);
if (line >= 0)
{
const char* contextEnd = context + ::strlen(context) - 1;
while (contextEnd > context && *contextEnd != '/')
contextEnd--;
if (*contextEnd == '/')
context = contextEnd + 1;
else
context = contextEnd;
}
if ( logType != LogType_SYSLOG )
{
#ifdef BEEGFS_DEBUG_PROFILING
uint64_t timeMicroS = nowTime.getTimeMicroSecPart(); // additional micro-s info for timestamp
if (line > 0)
fprintf(stdFile, "(%d) %s.%06ld %s [%s:%i] >> %s\n", level, timeStr, (long) timeMicroS,
threadName, context, line, msg);
else
fprintf(stdFile, "(%d) %s.%06ld %s [%s] >> %s\n", level, timeStr, (long) timeMicroS,
threadName, context, msg);
#else
if (line > 0)
fprintf(stdFile, "(%d) %s %s [%s:%i] >> %s\n", level, timeStr, threadName, context, line,
msg);
else
fprintf(stdFile, "(%d) %s %s [%s] >> %s\n", level, timeStr, threadName, context, msg);
#endif // BEEGFS_DEBUG_PROFILING
//fflush(stdFile); // no longer needed => line buf
currentNumStdLines.increase();
}
else
{
if (line > 0)
syslog(syslogLevelMapping[level], "%s [%s:%i] >> %s\n", threadName, context, line, msg);
else
syslog(syslogLevelMapping[level], "%s [%s] >> %s\n", threadName, context, msg);
}
}
/**
* Wrapper for logGrantedUnlocked that locks/unlocks the outputMutex.
*/
void Logger::logGranted(int level, const char* threadName, const char* context, int line,
const char* msg)
{
pthread_rwlock_rdlock(&this->rwLock);
logGrantedUnlocked(level, threadName, context, line, msg);
pthread_rwlock_unlock(&this->rwLock);
rotateStdLogChecked();
}
/**
* Prints a backtrage to the standard log.
*/
void Logger::logBacktraceGranted(const char* context, int backtraceLength, char** backtraceSymbols)
{
std::string threadName = PThread::getCurrentThreadName();
pthread_rwlock_rdlock(&this->rwLock);
logGrantedUnlocked(1, threadName.c_str(), context, -1, "Backtrace:");
for(int i=0; i < backtraceLength; i++)
{
fprintf(stdFile, "%d: %s\n", i+1, backtraceSymbols[i] );
}
pthread_rwlock_unlock(&this->rwLock);
}
/*
* Print time and date to buf.
*
* @param buf output buffer
* @param bufLen length of buf (depends on current locale, at least 32 recommended)
* @return 0 on error, strLen of buf otherwise
*/
size_t Logger::getTimeStr(uint64_t seconds, char* buf, size_t bufLen)
{
struct tm nowStruct;
localtime_r((time_t*) &seconds, &nowStruct);
size_t strRes = strftime(buf, bufLen, timeFormat, &nowStruct);
buf[strRes] = 0; // just to make sure - in case the given buf is too small
return strRes;
}
void Logger::prepareLogFiles()
{
if ( logType == LogType_SYSLOG )
{
//if its sylog skip the file operation
return;
}
else if(!logStdFile.length() )
{
stdFile = stdout;
}
else
{
rotateLogFile(logStdFile);
stdFile = fopen(logStdFile.c_str(), "w");
if(!stdFile)
{
perror("Logger::openStdLogFile");
stdFile = stdout;
throw InvalidConfigException(
std::string("Unable to create standard log file: ") + logStdFile);
}
}
setlinebuf(stdFile);
return;
}
void Logger::rotateLogFile(std::string filename)
{
for(int i=logNumRotatedFiles; i > 0; i--)
{
std::string oldname = filename +
( (i==1) ? "" : (rotatedFileSuffix + StringTk::intToStr(i-1) ) );
std::string newname = filename +
rotatedFileSuffix + StringTk::intToStr(i);
rename(oldname.c_str(), newname.c_str() );
}
}
void Logger::rotateStdLogChecked()
{
if( (stdFile == stdout) || logType == LogType_SYSLOG ||
!logNumLines || (currentNumStdLines.read() < logNumLines) )
return; // nothing to do yet
pthread_rwlock_wrlock(&this->rwLock);
if (currentNumStdLines.read() < logNumLines)
{ // we raced with another thread before aquiring the lock
pthread_rwlock_unlock(&this->rwLock);
return;
}
currentNumStdLines.setZero();
fclose(stdFile);
rotateLogFile(logStdFile); // the actual rotation
stdFile = fopen(logStdFile.c_str(), "w");
if(!stdFile)
{
perror("Logger::rotateStdLogChecked");
stdFile = stdout;
}
setlinebuf(stdFile);
pthread_rwlock_unlock(&this->rwLock);
}

View File

@@ -0,0 +1,380 @@
#pragma once
#include <common/app/config/ICommonConfig.h>
#include <common/app/config/InvalidConfigException.h>
#include <common/storage/StorageErrors.h>
#include <common/threading/Atomics.h>
#include <common/threading/PThread.h>
#include <common/Common.h>
#include <common/toolkit/StringTk.h>
#include <boost/io/ios_state.hpp>
#include <boost/preprocessor/comparison/equal.hpp>
#include <boost/preprocessor/if.hpp>
#include <boost/preprocessor/punctuation/is_begin_parens.hpp>
#include <boost/preprocessor/seq/elem.hpp>
#include <boost/preprocessor/seq/first_n.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/pop_front.hpp>
#include <boost/preprocessor/seq/rest_n.hpp>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/tuple/elem.hpp>
#include <boost/preprocessor/tuple/to_seq.hpp>
#include <system_error>
enum LogLevel
{
Log_ERR=0, /* system error */
Log_CRITICAL=1, /* something the users should definitely know about */
Log_WARNING=2, /* things that indicate or are related to a problem */
Log_NOTICE=3, /* things that could help finding problems */
Log_DEBUG=4, /* things that are only useful during debugging, often logged with LOG_DEBUG() */
Log_SPAM=5, /* things that are typically too detailed even during normal debugging,
very often with LOG_DEBUG() */
Log_LEVELMAX /* this needs to be the last entry; it's used as array length */
};
enum LogTopic
{
LogTopic_GENERAL=0, // default log topic
LogTopic_STATES=1, // everything related target/node states
LogTopic_MIRRORING=2, // everything related to mirroring
LogTopic_WORKQUEUES=3, // En/dequeueing of work items
LogTopic_STORAGEPOOLS=4, // related to storage pools
LogTopic_CAPACITY=5, // capacity (pool) information
LogTopic_COMMUNICATION=6,
LogTopic_QUOTA=7,
LogTopic_SESSIONS=8, // related to session(store) handling
LogTopic_EVENTLOGGER=9, // related to file event logger
LogTopic_DATABASE=10, // related to database operations
LogTopic_SOCKLIB=11, // socket library message (eg ib_lib)
LogTopic_INVALID
};
namespace beegfs { namespace logging {
struct SystemError
{
int value;
SystemError() : value(errno) {}
explicit SystemError(int value) : value(value) {}
SystemError operator()(int e) const { return SystemError(e); }
friend std::ostream& operator<<(std::ostream& os, SystemError e)
{
char errStrBuffer[256];
char* errStr = strerror_r(e.value, errStrBuffer, sizeof(errStrBuffer));
boost::io::ios_all_saver flags(os);
os.flags(std::ios_base::dec);
os.width(0);
return os << errStr << " (" << e.value << ")";
}
};
template<std::ios_base& (&Manip)(std::ios_base&)>
struct InBase
{
constexpr InBase() {}
template<typename T>
std::string operator()(const T& value) const
{
std::ostringstream out;
out << Manip << value;
return out.str();
}
};
struct LogInfos
{
std::stringstream infos;
template<typename T>
LogInfos& operator<<(T data)
{
infos << data;
return *this;
}
LogInfos& operator<<(bool data)
{
infos << (data ? "yes" : "no");
return *this;
}
/*
* std::error_code does have an operator<<, but this prints the numeric error code, not the
* error message, so we define our own one that pretty-prints message and category.
*/
LogInfos& operator<<(std::error_code err)
{
infos << err.message() << " (" << err.category().name() << ": " << err.value() << ")";
return *this;
}
std::string str() const
{
return infos.str();
}
};
}} // beegfs::logging
class Logger
{
private:
static const char* const LogTopics[LogTopic_INVALID];
private:
Logger(int defaultLevel, LogType cfgLogType, bool noDate, const std::string& stdFile,
unsigned linesPerFile, unsigned rotatedFiles);
public:
~Logger();
private:
static std::unique_ptr<Logger> logger;
// configurables
LogType logType;
IntVector logLevels;
bool logNoDate;
std::string logStdFile;
unsigned logNumLines;
unsigned logNumRotatedFiles;
// internals
pthread_rwlock_t rwLock; // cannot use RWLock class here, because that uses logging!
const char* timeFormat;
FILE* stdFile;
FILE* errFile;
AtomicUInt32 currentNumStdLines;
AtomicUInt64 currentNumErrLines;
std::string rotatedFileSuffix;
void logGrantedUnlocked(int level, const char* threadName, const char* context,
int line, const char* msg);
void logGranted(int level, const char* threadName, const char* context, int line,
const char* msg);
void logBacktraceGranted(const char* context, int backtraceLength, char** backtraceSymbols);
void prepareLogFiles();
size_t getTimeStr(uint64_t seconds, char* buf, size_t bufLen);
void rotateLogFile(std::string filename);
void rotateStdLogChecked();
public:
// inliners
void log(LogTopic logTopic, int level, const char* context, int line, const char* msg)
{
if(level > logLevels[logTopic])
return;
std::string threadName = PThread::getCurrentThreadName();
logGranted(level, threadName.c_str(), context, line, msg);
}
void log(LogTopic logTopic, int level, const char* context, const char* msg)
{
log(logTopic, level, context, -1, msg);
}
/**
* Just a wrapper for the normal log() method which takes "const char*" arguments.
*/
void log(LogTopic logTopic, int level, const std::string& context, const std::string& msg)
{
if(level > logLevels[logTopic])
return;
log(logTopic, level, context.c_str(), msg.c_str() );
}
void log(int level, const char* context, const char* msg)
{
log(LogTopic_GENERAL, level, context, msg);
}
void log(int level, const std::string& context, const std::string& msg)
{
log(level, context.c_str(), msg.c_str());
}
void logBacktrace(const char* context, int backtraceLength, char** backtraceSymbols)
{
if(!backtraceSymbols)
{
log(1, context, "Note: No symbols for backtrace available");
return;
}
logBacktraceGranted(context, backtraceLength, backtraceSymbols);
}
// getters & setters
/**
* Note: This method is not thread-safe.
*/
void setLogLevel(int logLevel, LogTopic logTopic = LogTopic_GENERAL)
{
if ((size_t)logTopic < logLevels.size())
logLevels.at(logTopic) = logLevel;
}
/**
* Note: This method is not thread-safe.
*/
int getLogLevel(LogTopic logTopic)
{
try
{
return logLevels.at(logTopic);
}
catch (const std::out_of_range& e)
{
return -1;
}
}
/**
* Note: This method is not thread-safe.
*/
const IntVector& getLogLevels() const
{
return logLevels;
}
static LogTopic logTopicFromName(const std::string& name)
{
const auto idx = std::find_if(
std::begin(LogTopics), std::end(LogTopics),
[&] (const char* c) { return c == name; });
if (idx == std::end(LogTopics))
return LogTopic_INVALID;
return LogTopic(idx - std::begin(LogTopics));
}
static std::string logTopicToName(LogTopic logTopic)
{
return LogTopics[logTopic];
}
static Logger* createLogger(int defaultLevel, LogType logType, bool noDate,
const std::string& stdFile, unsigned linesPerFile, unsigned rotatedFiles)
{
if (logger)
throw std::runtime_error("attempted to create a second system-wide logger");
logger.reset(new Logger(defaultLevel, logType, noDate, stdFile, linesPerFile,
rotatedFiles));
return logger.get();
}
static void destroyLogger()
{
logger.reset();
}
static Logger* getLogger()
{
return logger.get();
}
static bool isInitialized()
{
return !!logger;
}
};
#define LOG_CTX_TOP_L_ITEM__sep \
(_log_line << (_log_items++ ? "; " : " "))
#define LOG_CTX_TOP_L_ITEM__1(item) \
LOG_CTX_TOP_L_ITEM__sep << BOOST_PP_STRINGIZE(item) << ": " << item
#define LOG_CTX_TOP_L_ITEM__2(item) \
LOG_CTX_TOP_L_ITEM__sep << BOOST_PP_TUPLE_ELEM(0, item) << ": " \
<< BOOST_PP_TUPLE_ELEM(1, item)
#define LOG_CTX_TOP_L_ITEM__3(item) \
do { \
if (_log_level >= BOOST_PP_CAT(Log_, BOOST_PP_TUPLE_ELEM(0, item))) { \
LOG_CTX_TOP_L_ITEM__sep << BOOST_PP_TUPLE_ELEM(1, item) << ": " \
<< BOOST_PP_TUPLE_ELEM(2, item); \
} \
} while (0)
#define LOG_CTX_TOP_L_ITEM__bad(item) \
static_assert(false, "wrong number of arguments in log info, is " BOOST_PP_STRINGIZE(item))
#define LOG_CTX_TOP_L_ITEM(r, data, item) \
BOOST_PP_IF( \
BOOST_PP_IS_BEGIN_PARENS(item), \
BOOST_PP_IF( \
BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(item), 2), \
LOG_CTX_TOP_L_ITEM__2, \
BOOST_PP_IF( \
BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(item), 3), \
LOG_CTX_TOP_L_ITEM__3, \
LOG_CTX_TOP_L_ITEM__bad)), \
LOG_CTX_TOP_L_ITEM__1)(item);
#define LOG_CTX_TOP_L_ITEMS(items) \
do { \
BOOST_PP_SEQ_FOR_EACH(LOG_CTX_TOP_L_ITEM, , items) \
} while (0)
#define LOG_CTX_TOP_L(Topic, Level, Context, Line, Message, ...) \
do { \
Logger* const _log_logger = Logger::getLogger(); \
if (!_log_logger || _log_logger->getLogLevel(Topic) < Level) \
break; \
auto _log_level = _log_logger->getLogLevel(Topic); \
(void) _log_level; \
unsigned _log_items = 0; \
(void) _log_items; \
beegfs::logging::LogInfos _log_line; \
const beegfs::logging::SystemError sysErr; (void) sysErr; \
const beegfs::logging::InBase<std::hex> hex; (void) hex; \
const beegfs::logging::InBase<std::oct> oct; (void) oct; \
_log_line << Message; \
LOG_CTX_TOP_L_ITEMS( \
BOOST_PP_SEQ_POP_FRONT(BOOST_PP_TUPLE_TO_SEQ((, ##__VA_ARGS__)))); \
_log_logger->log(Topic, Level, Context, Line, _log_line.str().c_str()); \
} while (0)
#define LOG(Topic, Level, Message, ...) \
LOG_CTX_TOP_L(LogTopic_##Topic, Log_##Level, __FILE__, __LINE__, Message, ## __VA_ARGS__)
#define LOG_CTX(Topic, Level, Context, Message, ...) \
LOG_CTX_TOP_L(LogTopic_##Topic, Log_##Level, Context, -1, Message, ## __VA_ARGS__)
#ifdef BEEGFS_DEBUG
#define LOG_DEBUG_CTX LOG_CTX
// Context may be a std::string, a C string, or a string literal.
// &X[0] turns both into a const char*
#define LOG_DEBUG(Context, Level, Message) LOG_CTX_TOP_L(LogTopic_GENERAL, LogLevel(Level), \
&Context[0], -1, Message)
#define LOG_DBG LOG
#define LOG_TOP_DBG LOG_TOP
#define LOG_CTX_TOP_DBG LOG_CTX_TOP
#else
#define LOG_DEBUG_CTX(...) do {} while (0)
#define LOG_DEBUG(...) do {} while (0)
#define LOG_DBG(...) do {} while (0)
#define LOG_TOP_DBG(...) do {} while (0)
#define LOG_CTX_TOP_DBG(...) do {} while (0)
#endif

View File

@@ -0,0 +1,73 @@
#pragma once
#include <common/Common.h>
// defines storage benchmark errors...
#define STORAGEBENCH_ERROR_COM_TIMEOUT -3
#define STORAGEBENCH_ERROR_ABORT_BENCHMARK -2
#define STORAGEBENCH_ERROR_WORKER_ERROR -1
#define STORAGEBENCH_ERROR_NO_ERROR 0
#define STORAGEBENCH_ERROR_UNINITIALIZED 1
#define STORAGEBENCH_ERROR_INITIALIZATION_ERROR 10
#define STORAGEBENCH_ERROR_INIT_READ_DATA 11
#define STORAGEBENCH_ERROR_INIT_CREATE_BENCH_FOLDER 12
#define STORAGEBENCH_ERROR_INIT_TRANSFER_DATA 13
#define STORAGEBENCH_ERROR_RUNTIME_ERROR 20
#define STORAGEBENCH_ERROR_RUNTIME_DELETE_FOLDER 21
#define STORAGEBENCH_ERROR_RUNTIME_OPEN_FILES 22
#define STORAGEBENCH_ERROR_RUNTIME_UNKNOWN_TARGET 23
#define STORAGEBENCH_ERROR_RUNTIME_IS_RUNNING 24
#define STORAGEBENCH_ERROR_RUNTIME_CLEANUP_JOB_ACTIVE 25
// map for throughput results; key: targetID, value: throughput in kb/s
typedef std::map<uint16_t, int64_t> StorageBenchResultsMap;
typedef StorageBenchResultsMap::iterator StorageBenchResultsMapIter;
typedef StorageBenchResultsMap::const_iterator StorageBenchResultsMapCIter;
typedef StorageBenchResultsMap::value_type StorageBenchResultsMapVal;
/*
* enum for the action parameter of the storage benchmark
*/
enum StorageBenchAction
{
StorageBenchAction_START = 0,
StorageBenchAction_STOP = 1,
StorageBenchAction_STATUS = 2,
StorageBenchAction_CLEANUP = 3,
StorageBenchAction_NONE = 4
};
/*
* enum for the different benchmark types
*/
enum StorageBenchType
{
StorageBenchType_READ = 0,
StorageBenchType_WRITE = 1,
StorageBenchType_NONE = 2
};
/*
* enum for the states of the state machine of the storage benchmark operator
* note: see STORAGEBENCHSTATUS_IS_ACTIVE
*/
enum StorageBenchStatus
{
StorageBenchStatus_UNINITIALIZED = 0,
StorageBenchStatus_INITIALISED = 1,
StorageBenchStatus_ERROR = 2,
StorageBenchStatus_RUNNING = 3,
StorageBenchStatus_STOPPING = 4,
StorageBenchStatus_STOPPED = 5,
StorageBenchStatus_FINISHING = 6,
StorageBenchStatus_FINISHED = 7
};
#define STORAGEBENCHSTATUS_IS_ACTIVE(status) ( (status == StorageBenchStatus_RUNNING) || \
(status == StorageBenchStatus_FINISHING) || (status == StorageBenchStatus_STOPPING) )

View File

@@ -0,0 +1,506 @@
#include <common/threading/PThread.h>
#include <common/app/AbstractApp.h>
#include <common/toolkit/MessagingTk.h>
#include <common/toolkit/StringTk.h>
#include <common/toolkit/serialization/Serialization.h>
#include <common/toolkit/TimeAbs.h>
#include <common/net/message/control/DummyMsg.h>
#include "AbstractDatagramListener.h"
#include <mutex>
AbstractDatagramListener::AbstractDatagramListener(const std::string& threadName,
NetFilter* netFilter, NicAddressList& localNicList, AcknowledgmentStore* ackStore,
unsigned short udpPort, bool restrictOutboundInterfaces)
: PThread(threadName),
log(threadName),
netFilter(netFilter),
ackStore(ackStore),
restrictOutboundInterfaces(restrictOutboundInterfaces),
udpPort(udpPort),
sendBuf(NULL),
recvBuf(NULL),
recvTimeoutMS(4000),
localNicList(localNicList)
{
if(!initSocks() )
throw ComponentInitException("Unable to initialize the socket");
}
AbstractDatagramListener::~AbstractDatagramListener()
{
SAFE_FREE(sendBuf);
SAFE_FREE(recvBuf);
}
void AbstractDatagramListener::configSocket(StandardSocket* s, NicAddress* nicAddr, int bufsize)
{
s->setSoBroadcast(true);
s->setSoReuseAddr(true);
if (bufsize > 0)
s->setSoRcvBuf(bufsize);
if (nicAddr)
s->bindToAddr(nicAddr->ipAddr.s_addr, udpPort);
else
s->bind(udpPort);
/*
Note that if udpPort was intialized as zero, it will be set according to the
value used by the first created socket and any future sockets created by this method
will use that value. That may or may not work, depending upon the current socket
bindings.
*/
if(udpPort == 0)
{ // find out which port we are actually using
sockaddr_in bindAddr;
socklen_t bindAddrLen = sizeof(bindAddr);
int getSockNameRes = getsockname(s->getFD(), (sockaddr*)&bindAddr, &bindAddrLen);
if(getSockNameRes == -1)
throw SocketException("getsockname() failed: " + System::getErrString() );
udpPortNetByteOrder = bindAddr.sin_port;
udpPort = htons(bindAddr.sin_port);
}
std::string ifname;
if (nicAddr)
ifname = Socket::ipaddrToStr(nicAddr->ipAddr);
else
ifname = "any";
log.log(Log_NOTICE, std::string("Listening for UDP datagrams: ") + ifname + " Port " +
StringTk::intToStr(udpPort) );
}
/**
* mutex must be held in a multi-threaded scenario.
*/
bool AbstractDatagramListener::initSocks()
{
auto cfg = PThread::getCurrentThreadApp()->getCommonConfig();
int bufsize = cfg->getConnUDPRcvBufSize();
try
{
udpPortNetByteOrder = htons(udpPort);
loopbackAddrNetByteOrder = htonl(INADDR_LOOPBACK);
if (restrictOutboundInterfaces)
{
routingTable = PThread::getCurrentThreadApp()->getRoutingTable();
interfaceSocks.clear();
udpSock = nullptr;
ipSrcMap.clear();
for (auto& i : localNicList)
{
if (i.nicType == NICADDRTYPE_STANDARD)
{
std::shared_ptr<StandardSocket> s;
if (udpSock == nullptr)
{
udpSock = std::make_shared<StandardSocketGroup>(PF_INET, SOCK_DGRAM);
s = udpSock;
}
else
{
s = udpSock->createSubordinate(PF_INET, SOCK_DGRAM);
}
configSocket(s.get(), &i, bufsize);
interfaceSocks[i.ipAddr] = s;
}
}
}
else
{
// no need to close down any existing unbound UDP socket, it listens to all interfaces
if (udpSock == nullptr)
{
udpSock = std::make_shared<StandardSocketGroup>(PF_INET, SOCK_DGRAM);
configSocket(udpSock.get(), NULL, bufsize);
}
}
}
catch(SocketException& e)
{
log.logErr(std::string("UDP socket: ") + e.what() );
return false;
}
return true;
}
/**
* Note: This is for delayed buffer allocation for better NUMA performance.
*/
void AbstractDatagramListener::initBuffers()
{
void* bufInVoid = NULL;
void* bufOutVoid = NULL;
int inAllocRes = posix_memalign(&bufInVoid, sysconf(_SC_PAGESIZE), DGRAMMGR_RECVBUF_SIZE);
int outAllocRes = posix_memalign(&bufOutVoid, sysconf(_SC_PAGESIZE), DGRAMMGR_SENDBUF_SIZE);
IGNORE_UNUSED_VARIABLE(inAllocRes);
IGNORE_UNUSED_VARIABLE(outAllocRes);
this->recvBuf = (char*)bufInVoid;
this->sendBuf = (char*)bufOutVoid;
}
std::shared_ptr<StandardSocket> AbstractDatagramListener::findSenderSock(struct in_addr addr)
{
std::lock_guard<Mutex> lock(mutex);
return findSenderSockUnlocked(addr);
}
/**
* mutex must be held.
*/
std::shared_ptr<StandardSocket> AbstractDatagramListener::findSenderSockUnlocked(struct in_addr addr)
{
std::shared_ptr<StandardSocket> sock = udpSock;
if (restrictOutboundInterfaces)
{
if (addr.s_addr != loopbackAddrNetByteOrder)
{
struct in_addr k;
if (auto it { ipSrcMap.find(addr) }; it != std::end(ipSrcMap))
{
k = it->second;
}
else
{
if (!routingTable.match(addr, localNicList, k))
{
k.s_addr = 0;
// addr may have come from whoever sent a message, so this is a warning
LOG(COMMUNICATION, WARNING, "No routes found.", ("addr", Socket::ipaddrToStr(addr)));
}
ipSrcMap[addr] = k;
}
sock = interfaceSocks[k];
//LOG(COMMUNICATION, DEBUG, "findInterfaceSock", ("addr", Socket::ipaddrToStr(addr.s_addr).c_str()),
// ("k", Socket::ipaddrToStr(k).c_str()), ("sock", sock));
}
}
return sock;
}
void AbstractDatagramListener::setLocalNicList(NicAddressList& nicList)
{
const std::lock_guard<Mutex> lock(mutex);
localNicList = nicList;
LOG(COMMUNICATION, DEBUG, "setLocalNicList");
initSocks();
}
void AbstractDatagramListener::run()
{
try
{
registerSignalHandler();
initBuffers();
listenLoop();
log.log(Log_DEBUG, "Component stopped.");
}
catch(std::exception& e)
{
PThread::getCurrentThreadApp()->handleComponentException(e);
}
}
void AbstractDatagramListener::listenLoop()
{
struct sockaddr_in fromAddr;
while(!getSelfTerminate() )
{
socklen_t fromAddrLen = sizeof(fromAddr);
try
{
ssize_t recvRes = udpSock->recvfromT(
recvBuf, DGRAMMGR_RECVBUF_SIZE, 0, (struct sockaddr*)&fromAddr, &fromAddrLen,
recvTimeoutMS);
if(isDGramFromSelf(&fromAddr) )
{ // note: important if we're sending msgs to all hosts and don't want to include ourselves
//log.log(Log_SPAM, "Discarding DGram from localhost");
continue;
}
auto netMessageFactory = PThread::getCurrentThreadApp()->getNetMessageFactory();
auto msg = netMessageFactory->createFromRaw(recvBuf, recvRes);
if (msg->getMsgType() == NETMSGTYPE_Invalid
|| msg->getLength() != unsigned(recvRes)
|| msg->getSequenceNumber() != 0
|| msg->getSequenceNumberDone() != 0)
{
LOG(COMMUNICATION, NOTICE, "Received invalid message from peer",
("peer", Socket::ipaddrToStr(fromAddr.sin_addr)));
}
else
{
handleIncomingMsg(&fromAddr, msg.get());
}
}
catch(SocketTimeoutException& ste)
{
// nothing to worry about, just idle
}
catch(SocketInterruptedPollException& sipe)
{
// ignore interruption, because the debugger causes this
}
catch(SocketException& se)
{
log.logErr(std::string("Encountered an unrecoverable error: ") +
se.what() );
throw se; // to let the component exception handler be called
}
}
}
/**
* Send to all nodes (with a static number of retries).
*
* @param numRetries 0 to send only once
* @timeoutMS between retries (0 for default)
*/
void AbstractDatagramListener::sendToNodesUDP(const std::vector<NodeHandle>& nodes, NetMessage* msg,
int numRetries, int timeoutMS)
{
int retrySleepMS = timeoutMS ? timeoutMS : 750;
int numRetriesLeft = numRetries + 1;
if (unlikely(nodes.empty()))
return;
while (numRetriesLeft && !getSelfTerminate())
{
for (auto iter = nodes.begin(); iter != nodes.end(); iter++)
sendMsgToNode(**iter, msg);
numRetriesLeft--;
if(numRetriesLeft)
waitForSelfTerminateOrder(retrySleepMS);
}
}
/**
* Send to all active nodes.
*
* @param numRetries 0 to send only once
* @timeoutMS between retries (0 for default)
*/
void AbstractDatagramListener::sendToNodesUDP(const AbstractNodeStore* nodes, NetMessage* msg,
int numRetries, int timeoutMS)
{
sendToNodesUDP(nodes->referenceAllNodes(), msg, numRetries, timeoutMS);
}
/**
* Send to all nodes and wait for ack.
*
* @return true if all acks received within a timeout of ackWaitSleepMS.
*/
bool AbstractDatagramListener::sendToNodesUDPwithAck(const std::vector<NodeHandle>& nodes,
AcknowledgeableMsg* msg, int ackWaitSleepMS, int numRetries)
{
int numRetriesLeft = numRetries;
WaitAckMap waitAcks;
WaitAckMap receivedAcks;
WaitAckNotification notifier;
bool allAcksReceived = false;
// note: we use uint for tv_sec (not uint64) because 32 bits are enough here
std::string ackIDPrefix =
StringTk::uintToHexStr(TimeAbs().getTimeval()->tv_sec) + "-" +
StringTk::uintToHexStr(incAckCounter() ) + "-"
"dgramLis" "-";
if (unlikely(nodes.empty()))
return true;
// create and register waitAcks
for (auto iter = nodes.begin(); iter != nodes.end(); iter++)
{
std::string ackID(ackIDPrefix + (*iter)->getAlias() );
WaitAck waitAck(ackID, iter->get());
waitAcks.insert(WaitAckMapVal(ackID, waitAck) );
}
ackStore->registerWaitAcks(&waitAcks, &receivedAcks, &notifier);
// loop: send requests -> waitforcompletion -> resend
while(numRetriesLeft && !getSelfTerminate() )
{
// create waitAcks copy
WaitAckMap currentWaitAcks;
{
const std::lock_guard<Mutex> lock(notifier.waitAcksMutex);
currentWaitAcks = waitAcks;
}
// send messages
for(WaitAckMapIter iter = currentWaitAcks.begin(); iter != currentWaitAcks.end(); iter++)
{
Node* node = (Node*)iter->second.privateData;
msg->setAckID(iter->first.c_str() );
sendMsgToNode(*node, msg);
}
// wait for acks
allAcksReceived = ackStore->waitForAckCompletion(&currentWaitAcks, &notifier, ackWaitSleepMS);
if(allAcksReceived)
break; // all acks received
// some waitAcks left => prepare next loop
numRetriesLeft--;
}
// clean up
ackStore->unregisterWaitAcks(&waitAcks);
LOG_DBG(COMMUNICATION, DEBUG, "UDP msg ack stats", receivedAcks.size(), nodes.size());
return allAcksReceived;
}
/**
* Send to all active nodes and wait for ack.
* The message should not have an ackID set.
*
* @return true if all acks received within a reasonable timeout.
*/
bool AbstractDatagramListener::sendToNodesUDPwithAck(const AbstractNodeStore* nodes,
AcknowledgeableMsg* msg, int ackWaitSleepMS, int numRetries)
{
return sendToNodesUDPwithAck(nodes->referenceAllNodes(), msg, ackWaitSleepMS, numRetries);
}
/**
* Send to node and wait for ack.
* The message should not have an ackID set.
*
* @return true if ack received within a reasonable timeout.
*/
bool AbstractDatagramListener::sendToNodeUDPwithAck(const NodeHandle& node, AcknowledgeableMsg* msg,
int ackWaitSleepMS, int numRetries)
{
std::vector<NodeHandle> nodes(1, node);
return sendToNodesUDPwithAck(nodes, msg, ackWaitSleepMS, numRetries);
}
/**
* Sends the buffer to all available node interfaces.
* @return true if at least one buffer was sent
*/
bool AbstractDatagramListener::sendBufToNode(Node& node, const char* buf, size_t bufLen)
{
NicAddressList nicList(node.getNicList() );
unsigned short portUDP = node.getPortUDP();
bool sent = false;
for(NicAddressListIter iter = nicList.begin(); iter != nicList.end(); iter++)
{
if(iter->nicType != NICADDRTYPE_STANDARD)
continue;
if(!netFilter->isAllowed(iter->ipAddr.s_addr) )
continue;
if (sendto(buf, bufLen, 0, iter->ipAddr, portUDP) >0)
sent = true;
}
if (!sent)
LOG(COMMUNICATION, ERR, std::string("Failed to send buf"), ("node", node.getNodeIDWithTypeStr()));
return sent;
}
/**
* Sends the message to all available node interfaces.
*/
bool AbstractDatagramListener::sendMsgToNode(Node& node, NetMessage* msg)
{
const auto msgBuf = MessagingTk::createMsgVec(*msg);
return sendBufToNode(node, &msgBuf[0], msgBuf.size());
}
/**
* Note: This is intended to wake up the datagram listener thread and thus allow faster termination
* (so you will want to call this after setting self-terminate).
*/
void AbstractDatagramListener::sendDummyToSelfUDP()
{
in_addr hostAddr;
hostAddr.s_addr = loopbackAddrNetByteOrder;
DummyMsg msg;
const auto msgBuf = MessagingTk::createMsgVec(msg);
this->sendto(&msgBuf[0], msgBuf.size(), 0, hostAddr, udpPort);
}
/**
* Increase by one and return previous value.
*/
unsigned AbstractDatagramListener::incAckCounter()
{
return ackCounter.increase();
}
bool AbstractDatagramListener::isDGramFromSelf(struct sockaddr_in* fromAddr)
{
if(fromAddr->sin_port != udpPortNetByteOrder)
return false;
if (loopbackAddrNetByteOrder == fromAddr->sin_addr.s_addr)
return true;
const std::lock_guard<Mutex> lock(mutex);
for(NicAddressListIter iter = localNicList.begin(); iter != localNicList.end(); iter++)
{
//LogContext("isDGramFromSelf").log(Log_DEBUG, std::string("fromAddr=") + Socket::ipaddrToStr(&fromAddr->sin_addr)
//+ " nic=" + Socket::ipaddrToStr(&iter->ipAddr));
if(iter->ipAddr.s_addr == fromAddr->sin_addr.s_addr)
return true;
}
return false;
}

View File

@@ -0,0 +1,155 @@
#pragma once
#include <common/app/log/LogContext.h>
#include <common/threading/Atomics.h>
#include <common/threading/PThread.h>
#include <common/net/sock/StandardSocket.h>
#include <common/net/message/AcknowledgeableMsg.h>
#include <common/net/message/NetMessage.h>
#include <common/nodes/AbstractNodeStore.h>
#include <common/toolkit/AcknowledgmentStore.h>
#include <common/toolkit/NetFilter.h>
#include <common/Common.h>
#include "ComponentInitException.h"
#include <mutex>
#define DGRAMMGR_RECVBUF_SIZE 65536
#define DGRAMMGR_SENDBUF_SIZE DGRAMMGR_RECVBUF_SIZE
// forward declaration
class NetFilter;
typedef std::unordered_map<struct in_addr, std::shared_ptr<StandardSocket>, InAddrHash> StandardSocketMap;
class AbstractDatagramListener : public PThread
{
// for efficient individual multi-notifications (needs access to mutex)
friend class LockEntryNotificationWork;
friend class LockRangeNotificationWork;
public:
virtual ~AbstractDatagramListener();
void sendToNodesUDP(const AbstractNodeStore* nodes, NetMessage* msg, int numRetries,
int timeoutMS=0);
void sendToNodesUDP(const std::vector<NodeHandle>& nodes, NetMessage* msg, int numRetries,
int timeoutMS=0);
bool sendToNodesUDPwithAck(const AbstractNodeStore* nodes, AcknowledgeableMsg* msg,
int ackWaitSleepMS = 1000, int numRetries=2);
bool sendToNodesUDPwithAck(const std::vector<NodeHandle>& nodes, AcknowledgeableMsg* msg,
int ackWaitSleepMS = 1000, int numRetries=2);
bool sendToNodeUDPwithAck(const NodeHandle& node, AcknowledgeableMsg* msg,
int ackWaitSleepMS = 1000, int numRetries=2);
bool sendBufToNode(Node& node, const char* buf, size_t bufLen);
bool sendMsgToNode(Node& node, NetMessage* msg);
void sendDummyToSelfUDP();
private:
std::shared_ptr<StandardSocket> findSenderSockUnlocked(struct in_addr addr);
protected:
AbstractDatagramListener(const std::string& threadName, NetFilter* netFilter,
NicAddressList& localNicList, AcknowledgmentStore* ackStore, unsigned short udpPort,
bool restrictOutboundInterfaces);
LogContext log;
NetFilter* netFilter;
AcknowledgmentStore* ackStore;
bool restrictOutboundInterfaces;
unsigned short udpPort;
unsigned short udpPortNetByteOrder;
in_addr_t loopbackAddrNetByteOrder; // (because INADDR_... constants are in host byte order)
char* sendBuf;
virtual void handleIncomingMsg(struct sockaddr_in* fromAddr, NetMessage* msg) = 0;
std::shared_ptr<StandardSocket> findSenderSock(struct in_addr addr);
/**
* Returns the mutex related to seralization of sends.
*/
Mutex* getSendMutex()
{
return &mutex;
}
private:
std::shared_ptr<StandardSocketGroup> udpSock;
StandardSocketMap interfaceSocks;
IpSourceMap ipSrcMap;
char* recvBuf;
int recvTimeoutMS;
RoutingTable routingTable;
/**
* For now, use a single mutex for all of the members that are subject to
* thread contention. Using multiple mutexes makes the locking more difficult
* and can lead to deadlocks. The state dependencies are all intertwined,
* anyway.
*/
Mutex mutex;
AtomicUInt32 ackCounter; // used to generate ackIDs
NicAddressList localNicList;
bool initSocks();
void initBuffers();
void configSocket(StandardSocket* sock, NicAddress* nicAddr, int bufsize);
void run();
void listenLoop();
bool isDGramFromSelf(struct sockaddr_in* fromAddr);
unsigned incAckCounter();
public:
// inliners
/**
* Returns ENETUNREACH if no local NIC found to reach @to.
*/
ssize_t sendto(const void* buf, size_t len, int flags,
const struct sockaddr* to, socklen_t tolen)
{
const std::lock_guard<Mutex> lock(mutex);
struct in_addr a = reinterpret_cast<const struct sockaddr_in*>(to)->sin_addr;
std::shared_ptr<StandardSocket> s = findSenderSockUnlocked(a);
if (s == nullptr)
return ENETUNREACH;
return s->sendto(buf, len, flags, to, tolen);
}
/**
* Returns ENETUNREACH if no local NIC found to reach @ipAddr.
*/
ssize_t sendto(const void *buf, size_t len, int flags,
struct in_addr ipAddr, unsigned short port)
{
const std::lock_guard<Mutex> lock(mutex);
std::shared_ptr<StandardSocket> s = findSenderSockUnlocked(ipAddr);
if (s == nullptr)
return ENETUNREACH;
return s->sendto(buf, len, flags, ipAddr, port);
}
// getters & setters
void setRecvTimeoutMS(int recvTimeoutMS)
{
this->recvTimeoutMS = recvTimeoutMS;
}
unsigned short getUDPPort()
{
return udpPort;
}
void setLocalNicList(NicAddressList& nicList);
};

View File

@@ -0,0 +1,8 @@
#pragma once
#include <common/toolkit/NamedException.h>
#include <common/Common.h>
DECLARE_NAMEDEXCEPTION(ComponentInitException, "ComponentInitException")

View File

@@ -0,0 +1,44 @@
#include "RegistrationDatagramListener.h"
RegistrationDatagramListener::RegistrationDatagramListener(NetFilter* netFilter,
NicAddressList& localNicList, AcknowledgmentStore* ackStore, unsigned short udpPort,
bool restrictOutboundInterfaces) :
AbstractDatagramListener("RegDGramLis", netFilter, localNicList, ackStore, udpPort,
restrictOutboundInterfaces)
{
}
void RegistrationDatagramListener::handleIncomingMsg(struct sockaddr_in* fromAddr, NetMessage* msg)
{
HighResolutionStats stats; // currently ignored
std::shared_ptr<StandardSocket> sock = findSenderSock(fromAddr->sin_addr);
if (sock == nullptr)
{
log.log(Log_WARNING, "Could not handle incoming message: no socket");
return;
}
NetMessage::ResponseContext rctx(fromAddr, sock.get(), sendBuf, DGRAMMGR_SENDBUF_SIZE, &stats);
switch(msg->getMsgType() )
{
// valid messages within this context
case NETMSGTYPE_Ack:
case NETMSGTYPE_Heartbeat:
case NETMSGTYPE_Dummy:
{
if(!msg->processIncoming(rctx) )
log.log(Log_WARNING, "Problem encountered during handling of incoming message");
} break;
default:
{ // valid, but not within this context
log.log(Log_SPAM,
"Received a message that is invalid within the current context "
"from: " + Socket::ipaddrToStr(fromAddr->sin_addr) + "; "
"type: " + netMessageTypeToStr(msg->getMsgType() ) );
} break;
};
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <common/components/AbstractDatagramListener.h>
#include <common/Common.h>
/**
* This is a minimalistic version of a datagram listener, which only handles incoming heartbeat
* messages.
* This component is required during registration to wait for a management server heartbeat, but to
* also make sure that we don't receive/handle an yincoming messages that could access data
* structures (e.g. app->localNode), which are not fully initialized prior to registration
* completion.
*/
class RegistrationDatagramListener : public AbstractDatagramListener
{
public:
RegistrationDatagramListener(NetFilter* netFilter, NicAddressList& localNicList,
AcknowledgmentStore* ackStore, unsigned short udpPort,
bool restrictOutboundInterfaces);
protected:
virtual void handleIncomingMsg(struct sockaddr_in* fromAddr, NetMessage* msg);
private:
};

View File

@@ -0,0 +1,86 @@
#include <common/toolkit/TimeAbs.h>
#include "StatsCollector.h"
#include <mutex>
StatsCollector::StatsCollector(MultiWorkQueue* workQ, unsigned collectIntervalMS,
unsigned historyLength)
: PThread("Stats"),
log("Stats"),
workQ(workQ),
collectIntervalMS(collectIntervalMS),
historyLength(historyLength)
{ }
StatsCollector::~StatsCollector()
{
// nothing to be done here
}
void StatsCollector::run()
{
try
{
registerSignalHandler();
collectLoop();
log.log(Log_DEBUG, "Component stopped.");
}
catch(std::exception& e)
{
PThread::getCurrentThreadApp()->handleComponentException(e);
}
}
void StatsCollector::collectLoop()
{
while(!waitForSelfTerminateOrder(collectIntervalMS) )
{
collectStats();
}
}
void StatsCollector::collectStats()
{
HighResolutionStats currentStats;
std::lock_guard<Mutex> mutexLock(mutex);
// Note: Newer stats in the internal list are pushed at the front side
// get old stats and reset them
workQ->getAndResetStats(&currentStats);
// set current stats time
currentStats.rawVals.statsTimeMS = TimeAbs().getTimeMS();
// take care of max history length
if(statsList.size() == historyLength)
statsList.pop_back();
// push new stats to front
statsList.push_front(currentStats);
}
/**
* Returns the part of the stats history after lastStatsMS.
*/
void StatsCollector::getStatsSince(uint64_t lastStatsMS, HighResStatsList& outStatsList)
{
std::lock_guard<Mutex> mutexLock(mutex);
// Note: Newer stats in the internal list are pushed at the front side, but
// newer stats on the outStatsList are pushed to the back side.
for(HighResStatsListIter iter = statsList.begin(); iter != statsList.end(); iter++)
{
// are the current stats older than requested?
if(iter->rawVals.statsTimeMS <= lastStatsMS)
break;
outStatsList.push_back(*iter);
}
}

View File

@@ -0,0 +1,51 @@
#pragma once
#include <common/app/log/LogContext.h>
#include <common/app/AbstractApp.h>
#include <common/components/worker/queue/MultiWorkQueue.h>
#include <common/components/ComponentInitException.h>
#include <common/threading/PThread.h>
#include <common/net/message/NetMessage.h>
#include <common/Common.h>
#define STATSCOLLECTOR_COLLECT_INTERVAL_MS 1000
#define STATSCOLLECTOR_HISTORY_LENGTH 60
class StatsCollector : public PThread
{
public:
StatsCollector(MultiWorkQueue* workQ, unsigned collectIntervalMS, unsigned historyLength);
virtual ~StatsCollector();
void getStatsSince(uint64_t lastStatsMS, HighResStatsList& outStatsList);
protected:
LogContext log;
Mutex mutex;
HighResStatsList statsList;
MultiWorkQueue* workQ; // might be NULL in derived classes with own collectStats()
unsigned collectIntervalMS;
unsigned historyLength;
virtual void collectStats();
private:
virtual void run();
void collectLoop();
public:
// getters & setters
};

View File

@@ -0,0 +1,619 @@
#include <common/app/AbstractApp.h>
#include <common/toolkit/StringTk.h>
#include "worker/IncomingDataWork.h"
#include "StreamListener.h"
#include <sys/epoll.h>
#define EPOLL_EVENTS_NUM (512) /* make it big to avoid starvation of higher FDs */
#define RDMA_CHECK_FORCE_POLLLOOPS (7200) /* to avoid calling the check routine in each loop */
#define RDMA_CHECK_INTERVAL_MS (150*60*1000) /* 150mins (must be more than double of the
client-side idle disconnect interval to avoid cases where
server disconnects first) */
#define SOCKRETURN_SOCKS_NUM (32)
StreamListener::StreamListener(NicAddressList& localNicList, MultiWorkQueue* workQueue,
unsigned short listenPort)
: PThread("StreamLis"),
log("StreamLis"),
workQueue(workQueue),
rdmaCheckForceCounter(0)
{
NicListCapabilities localNicCaps;
NetworkInterfaceCard::supportedCapabilities(&localNicList, &localNicCaps);
this->epollFD = epoll_create(10); // "10" is just a hint (and is actually ignored)
if(epollFD == -1)
{
throw ComponentInitException(std::string("Error during epoll_create(): ") +
System::getErrString() );
}
if(!initSockReturnPipe() )
throw ComponentInitException("Unable to initialize sock return pipe");
if(!initSocks(listenPort, &localNicCaps) )
throw ComponentInitException("Unable to initialize socket");
}
StreamListener::~StreamListener()
{
pollList.remove(sockReturnPipe->getReadFD() ); // may not be deleted by deleteAllConns()
delete(sockReturnPipe);
deleteAllConns();
if(epollFD != -1)
close(epollFD);
}
bool StreamListener::initSockReturnPipe()
{
this->sockReturnPipe = new Pipe(false, true);
pollList.add(sockReturnPipe->getReadFD() );
struct epoll_event epollEvent;
epollEvent.events = EPOLLIN;
epollEvent.data.ptr = sockReturnPipe->getReadFD();
if(epoll_ctl(epollFD, EPOLL_CTL_ADD, sockReturnPipe->getReadFD()->getFD(), &epollEvent) == -1)
{
log.logErr(std::string("Unable to add sock return pipe (read side) to epoll set: ") +
System::getErrString() );
return false;
}
return true;
}
bool StreamListener::initSocks(unsigned short listenPort, NicListCapabilities* localNicCaps)
{
auto cfg = PThread::getCurrentThreadApp()->getCommonConfig();
rdmaListenSock = NULL;
tcpListenSock = NULL;
// RDMA
if(localNicCaps->supportsRDMA)
{ // RDMA usage is enabled
try
{
rdmaListenSock = RDMASocket::create().release();
rdmaListenSock->bind(listenPort);
rdmaListenSock->listen();
pollList.add(rdmaListenSock);
struct epoll_event epollEvent;
epollEvent.events = EPOLLIN;
epollEvent.data.ptr = rdmaListenSock;
if(epoll_ctl(epollFD, EPOLL_CTL_ADD, rdmaListenSock->getFD(), &epollEvent) == -1)
{
log.logErr(std::string("Unable to add RDMA listen sock to epoll set: ") +
System::getErrString() );
return false;
}
log.log(3, std::string("Listening for RDMA connections: Port ") +
StringTk::intToStr(listenPort) );
}
catch(SocketException& e)
{
log.logErr(std::string("RDMA socket: ") + e.what() );
return false;
}
}
// TCP
try
{
tcpListenSock = new StandardSocket(PF_INET, SOCK_STREAM);
tcpListenSock->setSoReuseAddr(true);
int bufsize = cfg->getConnTCPRcvBufSize();
if (bufsize > 0)
tcpListenSock->setSoRcvBuf(bufsize);
tcpListenSock->bind(listenPort);
tcpListenSock->listen();
pollList.add(tcpListenSock);
struct epoll_event epollEvent;
epollEvent.events = EPOLLIN;
epollEvent.data.ptr = tcpListenSock;
if(epoll_ctl(epollFD, EPOLL_CTL_ADD, tcpListenSock->getFD(), &epollEvent) == -1)
{
log.logErr(std::string("Unable to add TCP listen sock to epoll set: ") +
System::getErrString() );
return false;
}
log.log(Log_NOTICE, std::string("Listening for TCP connections: Port ") +
StringTk::intToStr(listenPort) );
}
catch(SocketException& e)
{
log.logErr(std::string("TCP socket: ") + e.what() );
return false;
}
return true;
}
void StreamListener::run()
{
try
{
registerSignalHandler();
listenLoop();
log.log(4, "Component stopped.");
}
catch(std::exception& e)
{
PThread::getCurrentThreadApp()->handleComponentException(e);
}
}
void StreamListener::listenLoop()
{
const int epollTimeoutMS = 3000;
struct epoll_event epollEvents[EPOLL_EVENTS_NUM];
// (just to have 'em on the stack)
const int epollFD = this->epollFD;
RDMASocket* rdmaListenSock = this->rdmaListenSock;
StandardSocket* tcpListenSock = this->tcpListenSock;
FileDescriptor* sockReturnPipeReadEnd = this->sockReturnPipe->getReadFD();
bool runRDMAConnIdleCheck = false; // true just means we call the method (not enforce the check)
// wait for incoming events and handle them...
while(!getSelfTerminate() )
{
//log.log(4, std::string("Before poll(). pollArrayLen: ") +
// StringTk::uintToStr(pollArrayLen) );
int epollRes = epoll_wait(epollFD, epollEvents, EPOLL_EVENTS_NUM, epollTimeoutMS);
if(unlikely(epollRes < 0) )
{ // error occurred
if(errno == EINTR) // ignore interruption, because the debugger causes this
continue;
log.logErr(std::string("Unrecoverable epoll_wait error: ") + System::getErrString() );
break;
}
else
if(unlikely(!epollRes || (rdmaCheckForceCounter++ > RDMA_CHECK_FORCE_POLLLOOPS) ) )
{ // epollRes==0 is nothing to worry about, just idle
// note: we can't run idle check here directly because the check might modify the
// poll set, which will be accessed in the loop below
runRDMAConnIdleCheck = true;
}
// handle incoming data & connection attempts
for(size_t i=0; i < (size_t)epollRes; i++)
{
struct epoll_event* currentEvent = &epollEvents[i];
Pollable* currentPollable = (Pollable*)currentEvent->data.ptr;
//log.log(4, std::string("Incoming data on FD: ") +
// StringTk::intToStr(pollArray[i].fd) ); // debug in
if(unlikely(currentPollable == rdmaListenSock) )
onIncomingRDMAConnection(rdmaListenSock);
else
if(unlikely(currentPollable == tcpListenSock) )
onIncomingStandardConnection(tcpListenSock);
else
if(currentPollable == sockReturnPipeReadEnd)
onSockReturn();
else
onIncomingData( (Socket*)currentPollable);
}
if(unlikely(runRDMAConnIdleCheck) )
{ // note: whether check actually happens depends on elapsed time since last check
runRDMAConnIdleCheck = false;
rdmaConnIdleCheck();
}
}
}
/**
* Accept the incoming connection and add the new socket to the pollList.
*/
void StreamListener::onIncomingStandardConnection(StandardSocket* sock)
{
try
{
struct sockaddr_in peerAddr;
socklen_t peerAddrLen = sizeof(peerAddr);
std::unique_ptr<StandardSocket> acceptedSock(
(StandardSocket*)sock->accept( (struct sockaddr*)&peerAddr, &peerAddrLen));
// (note: level Log_DEBUG to avoid spamming the log until we have log topics)
log.log(Log_DEBUG, std::string("Accepted new connection from ") +
Socket::endpointAddrToStr(&peerAddr) +
std::string(" [SockFD: ") + StringTk::intToStr(acceptedSock->getFD() ) +
std::string("]") );
applySocketOptions(acceptedSock.get());
struct epoll_event epollEvent;
epollEvent.events = EPOLLIN | EPOLLONESHOT | EPOLLET;
epollEvent.data.ptr = acceptedSock.get();
if(epoll_ctl(this->epollFD, EPOLL_CTL_ADD, acceptedSock->getFD(), &epollEvent) == -1)
{
throw SocketException(std::string("Unable to add sock to epoll set: ") +
System::getErrString() );
}
pollList.add(acceptedSock.release());
}
catch(SocketException& se)
{
log.logErr(std::string("Trying to continue after connection accept error: ") + se.what() );
}
}
/**
* Accept the incoming connection and add the new socket to the pollList.
*/
void StreamListener::onIncomingRDMAConnection(RDMASocket* sock)
{
// accept the incoming connection (and loop until no more delayed events are waiting on
// this socket)
// (Note: RDMASockets use this internally also to handle other kindes of events)
// loop: check whether this is a false alarm, try to accept a new connection (if
// available) and also check for further events after accept
while(sock->checkDelayedEvents() )
{
try
{
struct sockaddr_in peerAddr;
socklen_t peerAddrLen = sizeof(peerAddr);
RDMASocket* acceptedSock =
(RDMASocket*)sock->accept( (struct sockaddr*)&peerAddr, &peerAddrLen);
// note: RDMASocket::accept() might return NULL (which is not an error)
if(!acceptedSock)
{
log.log(4, std::string("Ignoring an internal event on the listening RDMA socket") );
continue;
}
// (note: level Log_DEBUG to avoid spamming the log until we have log topics)
log.log(Log_DEBUG, std::string("Accepted new RDMA connection from ") +
Socket::endpointAddrToStr(&peerAddr) +
std::string(" [SockFD: ") + StringTk::intToStr(acceptedSock->getFD() ) +
std::string("]") );
struct epoll_event epollEvent;
epollEvent.events = EPOLLIN | EPOLLONESHOT | EPOLLET;
epollEvent.data.ptr = acceptedSock;
if(epoll_ctl(this->epollFD, EPOLL_CTL_ADD, acceptedSock->getFD(), &epollEvent) == -1)
{
throw SocketException(std::string("Unable to add RDMA sock to epoll set: ") +
System::getErrString() );
}
pollList.add(acceptedSock);
}
catch(SocketException& se)
{
log.logErr(std::string("Trying to continue after RDMA connection accept error: ") +
se.what() );
}
}
}
/**
* Add the socket to the workQueue.
*
* Note: Data can also be added to the queue by the
* IncomingDataWork::checkRDMASocketImmediateData() method.
*/
void StreamListener::onIncomingData(Socket* sock)
{
// check whether this is just a false alarm from a RDMASocket
if( (sock->getSockType() == NICADDRTYPE_RDMA) &&
isFalseAlarm( (RDMASocket*)sock) )
{
return;
}
// we really have incoming data => add work to queue
LOG_DEBUG("StreamListener::onIncomingData", 4,
std::string("Incoming stream data from: ") + sock->getPeername() );
int sockFD = sock->getFD(); // note: we store this here for delayed pollList removal, because
// this might be disconnect work, so the sock gets deleted by the worker and thus "sock->"
// becomes invalid
//log.log(4, "Creating new work for to the queue");
IncomingDataWork* work = new IncomingDataWork(this, sock);
//log.log(4, "Adding new work to the queue");
sock->setHasActivity(); // mark as active (for idle disconnect check)
if(sock->getIsDirect() )
workQueue->addDirectWork(work);
else
workQueue->addIndirectWork(work);
//log.log(4, "Added new work to the queue");
// note: no need to remove sock from epoll set, because we use edge-triggered mode with oneshot
// flag (which disables further events after first one has been reported). a sock that is
// closed by a worker is not a problem, because it will automatically be removed from the
// epoll set by the kernel.
// we just need to re-arm the epoll entry upon sock return.
pollList.removeByFD(sockFD);
}
/**
* Receive pointer to returned socket through the sockReturnPipe and
* re-add it to the pollList.
*/
void StreamListener::onSockReturn()
{
Socket* socks[SOCKRETURN_SOCKS_NUM];
ssize_t readRes = sockReturnPipe->getReadFD()->read(&socks, sizeof(socks) );
for(size_t i=0; ; i++)
{
Socket* currentSock = socks[i];
if(unlikely(readRes < (ssize_t)sizeof(Socket*) ) )
{ // recv the rest of the socket pointer
char* currentSockChar = (char*)currentSock;
sockReturnPipe->getReadFD()->readExact(
&currentSockChar[readRes], sizeof(Socket*)-readRes);
readRes = sizeof(Socket*);
}
//LOG_DEBUG("StreamListener::onSockReturn", 5,
// std::string("Socket returned (through pipe). SockFD: ") +
// StringTk::intToStr(currentSock->getFD() ) );
struct epoll_event epollEvent;
epollEvent.events = EPOLLIN | EPOLLONESHOT | EPOLLET;
epollEvent.data.ptr = currentSock;
int epollRes = epoll_ctl(this->epollFD, EPOLL_CTL_MOD, currentSock->getFD(), &epollEvent);
if(unlikely(epollRes == -1) )
{ // error
log.logErr("Unable to re-arm sock in epoll set: " + System::getErrString() );
log.log(3, "Disconnecting: " + currentSock->getPeername() );
delete(currentSock);
}
else
{
pollList.add(currentSock);
}
readRes -= sizeof(Socket*);
if(!readRes)
break;
}
}
/**
* Does not really check but instead just drops connections that have been idle for some time.
* Note: Actual checking is not performed to avoid timeout delays in the StreamListener.
*/
void StreamListener::rdmaConnIdleCheck()
{
const unsigned checkIntervalMS = RDMA_CHECK_INTERVAL_MS;
if(!this->rdmaListenSock)
return;
if(rdmaCheckT.elapsedMS() < checkIntervalMS)
{
this->rdmaCheckForceCounter = 0;
return;
}
//LOG_DEBUG("StreamListener::rdmaConnIdleCheck", 5, std::string("Performing check...") );
int rdmaListenFD = rdmaListenSock->getFD();
int numCheckedConns = 0;
IntList disposalFDs;
PollMap* pollMap = pollList.getPollMap();
// walk over all rdma socks, check conn status, delete idle socks and add them to
// the disposal list (note: we do not modify the pollList/pollMap yet)
for(PollMapIter iter = pollMap->begin(); iter != pollMap->end(); iter++)
{
int currentFD = iter->first;
if(currentFD == sockReturnPipe->getReadFD()->getFD() )
continue;
Socket* currentSock = (Socket*)iter->second;
if(currentSock->getSockType() != NICADDRTYPE_RDMA)
continue;
if(iter->first == rdmaListenFD)
continue;
numCheckedConns++;
// rdma socket => check activity
if(currentSock->getHasActivity() )
{ // conn had activity since last check => reset activity flag
currentSock->resetHasActivity();
}
else
{ // conn was inactive the whole time => disconnect and add to disposal list
log.log(Log_DEBUG,
"Disconnecting idle RDMA connection: " + currentSock->getPeername() );
delete(currentSock);
disposalFDs.push_back(iter->first);
}
}
// remove idle socks from pollMap
for(IntListIter iter = disposalFDs.begin(); iter != disposalFDs.end(); iter++)
{
pollList.removeByFD(*iter);
}
if (!disposalFDs.empty())
{ // logging
LOG_DEBUG("StreamListener::rdmaConnIdleCheck", 5, std::string("Check results: ") +
std::string("disposed ") + StringTk::int64ToStr(disposalFDs.size() ) + "/" +
StringTk::int64ToStr(numCheckedConns) );
IGNORE_UNUSED_VARIABLE(numCheckedConns);
}
// reset time counter
rdmaCheckT.setToNow();
}
void StreamListener::applySocketOptions(StandardSocket* sock)
{
LogContext log("StreamListener (apply socket options)");
try
{
sock->setTcpCork(false);
}
catch(SocketException& e)
{
log.log(Log_NOTICE, "Failed to disable TcpCork");
}
try
{
sock->setTcpNoDelay(true);
}
catch(SocketException& e)
{
log.log(Log_NOTICE, "Failed to enable TcpNoDelay");
}
try
{
sock->setSoKeepAlive(true);
}
catch(SocketException& e)
{
log.log(Log_NOTICE, "Failed to enable SoKeepAlive");
}
}
/**
* RDMA sockets might signal incoming events that are not actually incoming data, but instead
* handled internally in the IBVSocket class.
* In the latter case, we should not call recv() on the socket, because that would hang. So we need
* this method to decide wheter we put this socket into the incoming data queue or not.
*
* Note: A special case is a connection error that is detected during the false alarm
* check. We return true here, because the disconnect is handled internally so that
* no further steps are required by the caller.
*
* @return true in case of a false alarm (which means that no incoming data for recv is
* immediately available) or in case of an error; false otherwise (i.e. the caller can call recv()
* on this socket without blocking)
*/
bool StreamListener::isFalseAlarm(RDMASocket* sock)
{
const char* logContext = "StreamListener (incoming RDMA check)";
try
{
if(!sock->nonblockingRecvCheck() )
{ // false alarm
LOG_DEBUG(logContext, Log_SPAM,
std::string("Ignoring false alarm from: ") + sock->getPeername() );
// we use one-shot mechanism, so we need to re-arm the socket after an event notification
struct epoll_event epollEvent;
epollEvent.events = EPOLLIN | EPOLLONESHOT | EPOLLET;
epollEvent.data.ptr = sock;
int epollRes = epoll_ctl(this->epollFD, EPOLL_CTL_MOD, sock->getFD(), &epollEvent);
if(unlikely(epollRes == -1) )
{ // error
log.logErr("Unable to re-arm socket in epoll set: " + System::getErrString() );
log.log(Log_NOTICE, "Disconnecting: " + sock->getPeername() );
delete(sock);
}
return true;
}
// we really have incoming data
return false;
}
catch(SocketDisconnectException& e)
{ // a disconnect here is nothing to worry about
LogContext(logContext).log(Log_DEBUG,
std::string(e.what() ) );
}
catch(SocketException& e)
{
LogContext(logContext).log(Log_NOTICE,
std::string("Connection error: ") + sock->getPeername() + std::string(": ") +
std::string(e.what() ) );
}
// conn error occurred
pollList.remove(sock);
delete(sock);
return true;
}
void StreamListener::deleteAllConns()
{
PollMap* pollMap = pollList.getPollMap();
for(PollMapIter iter = pollMap->begin(); iter != pollMap->end(); iter++)
{
delete(iter->second);
}
}

View File

@@ -0,0 +1,69 @@
#pragma once
#include <common/app/log/LogContext.h>
#include <common/components/worker/queue/MultiWorkQueue.h>
#include <common/components/ComponentInitException.h>
#include <common/net/sock/StandardSocket.h>
#include <common/net/sock/RDMASocket.h>
#include <common/net/message/NetMessage.h>
#include <common/nodes/Node.h>
#include <common/threading/PThread.h>
#include <common/toolkit/poll/PollList.h>
#include <common/toolkit/Pipe.h>
#include <common/Common.h>
class StreamListener : public PThread
{
public:
StreamListener(NicAddressList& localNicList, MultiWorkQueue* workQueue,
unsigned short listenPort);
virtual ~StreamListener();
private:
LogContext log;
StandardSocket* tcpListenSock;
RDMASocket* rdmaListenSock;
MultiWorkQueue* workQueue;
int epollFD;
PollList pollList;
Pipe* sockReturnPipe;
Time rdmaCheckT;
int rdmaCheckForceCounter;
bool initSockReturnPipe();
bool initSocks(unsigned short listenPort, NicListCapabilities* localNicCaps);
virtual void run();
void listenLoop();
void onIncomingStandardConnection(StandardSocket* sock);
void onIncomingRDMAConnection(RDMASocket* sock);
void onIncomingData(Socket* sock);
void onSockReturn();
void rdmaConnIdleCheck();
void applySocketOptions(StandardSocket* sock);
bool isFalseAlarm(RDMASocket* sock);
void deleteAllConns();
public:
// getters & setters
FileDescriptor* getSockReturnFD()
{
return sockReturnPipe->getWriteFD();
}
MultiWorkQueue* getWorkQueue() const
{
return this->workQueue;
}
};

View File

@@ -0,0 +1,282 @@
#include "TimerQueue.h"
#include <common/toolkit/StringTk.h>
#include <mutex>
void TimerWorkList::enqueue(std::function<void ()> fn)
{
std::unique_lock<Mutex> lock(mutex);
available.push_back(std::move(fn));
condition.signal();
}
std::pair<bool, std::function<void ()>> TimerWorkList::deque(int timeoutMS)
{
std::unique_lock<Mutex> lock(mutex);
if (available.empty())
{
if (!condition.timedwait(&mutex, timeoutMS))
return {false, {}};
}
if (available.empty())
return {false, {}};
std::function<void ()> fn = std::move(available.front());
available.pop_front();
return {true, std::move(fn)};
}
size_t TimerWorkList::waitingItems() const
{
std::unique_lock<Mutex> lock(mutex);
return available.size();
}
TimerWorker::TimerWorker(TimerQueue& queue, TimerWorkList& workList, unsigned id, bool keepAlive):
PThread("TimerWork/" + StringTk::uintToStr(id)), queue(&queue), workList(&workList),
keepAlive(keepAlive), id(id), state(State_STOPPED)
{
}
TimerWorker::~TimerWorker()
{
stop();
}
void TimerWorker::start()
{
if (state.read() == State_RUNNING)
return;
if (state.read() == State_STOPPING)
join();
state.set(State_RUNNING);
try
{
PThread::start();
}
catch (...)
{
state.set(State_STOPPED);
throw;
}
}
void TimerWorker::stop()
{
if (state.read() == State_STOPPED)
return;
selfTerminate();
join();
state.set(State_STOPPED);
}
void TimerWorker::run()
{
static const unsigned suicideTimeoutMs = 10000;
while (!getSelfTerminate())
{
// try twice to dequeue an item - once not waiting at all, once waiting the full idle timeout.
// otherwise, an empty queue will cause worker threads to die immediatly, which is not what
// we want.
auto item = workList->deque(0);
if (!item.first)
item = workList->deque(suicideTimeoutMs);
if (!item.first && !keepAlive)
break;
if (item.first && item.second)
item.second();
}
state.set(State_STOPPING);
queue->deactivateWorker(this);
}
TimerQueue::TimerQueue(unsigned minPoolSize, unsigned maxPoolSize)
: PThread("TimerQ"), entrySequence(0)
{
for (unsigned i = 0; i < maxPoolSize; i++)
{
workerPool[i].reset(new TimerWorker(*this, workList, i, i < minPoolSize));
inactiveWorkers[i] = workerPool[i].get();
}
}
TimerQueue::~TimerQueue()
{
if (getID())
{
selfTerminate();
condition.signal();
join();
}
for (auto it = workerPool.begin(); it != workerPool.end(); ++it)
{
if (!it->second->isRunning())
continue;
it->second->selfTerminate();
}
for (auto it = workerPool.begin(); it != workerPool.end(); ++it)
workList.enqueue({});
for (auto it = workerPool.begin(); it != workerPool.end(); ++it)
it->second->stop();
}
/**
* Enqueues a function for execution at some point in the future. All enqueued items are
* executed by the same thread; to avoid one item blocking other items, each item should
* execute for as little time as possible.
*
* @timeout timeout (in milliseconds) after which to execute the function
* @action function to execute
* @returns handle that can be passed to cancel
*/
TimerQueue::EntryHandle TimerQueue::enqueue(std::chrono::milliseconds timeout,
std::function<void ()> action)
{
std::lock_guard<Mutex> lock(mutex);
std::pair<TimePoint, uint64_t> key{Clock::now() + timeout, entrySequence};
entrySequence += 1;
queue.insert({key, std::move(action)});
condition.signal();
return {*this, key};
}
/**
* Cancels a deferred function. Has no effect if the function has already started executing.
*
* @handle handle to deferred function, must be acquired by enqueue
*/
void TimerQueue::cancel(const EntryHandle& handle)
{
std::lock_guard<Mutex> lock(mutex);
// assert(handle.queue == &queue)
// assert(queue.count(handle.key))
queue.erase(handle.key);
}
void TimerQueue::run()
{
using namespace std::chrono;
std::unique_lock<Mutex> lock(mutex);
static const int idleTimeout = 60 * 1000;
static const int workerRetryTimeout = 10;
static const int activeTimeout = 0;
int waitTimeout = idleTimeout;
while (!getSelfTerminate())
{
// wait one minute if nothing is queued, otherwise wait for at most one minute
if (queue.empty())
{
condition.timedwait(&mutex, waitTimeout);
}
else
{
auto timeToFirstAction = queue.begin()->first.first - Clock::now();
// round up to next millisecond, based on the finest specified duration type
if (duration_cast<nanoseconds>(timeToFirstAction).count() % 1000000 > 0)
timeToFirstAction += milliseconds(1);
if (timeToFirstAction.count() > 0)
condition.timedwait(
&mutex,
std::min<Clock::rep>(
duration_cast<milliseconds>(timeToFirstAction).count(),
waitTimeout));
}
waitTimeout = idleTimeout;
// if we have queued items, we can be in one of two situations:
// * we have scheduled the item a short time ago, but no worker thread has dequeued it yet
// * we have scheduled the item a waitTimeout time ago, but no worker has dequeued it yet
//
// in both cases, we will want to wake up (hopefully) enough workers to process the items
// that are currently waiting, since we will only add more to the queue.
if (workList.waitingItems() > 0)
{
if (!requestWorkers(workList.waitingItems()))
waitTimeout = workerRetryTimeout;
}
if (queue.empty())
continue;
bool processedAny = false;
// do not process more items than we had in the queue to begin with. if we do not limit this,
// an item that requeues itself with delay 0 would never allow the thread to terminate.
for (auto size = queue.size(); size > 0; size--)
{
auto earliestQueued = queue.begin();
if (earliestQueued->first.first > Clock::now())
break;
auto action = std::move(earliestQueued->second);
queue.erase(earliestQueued);
workList.enqueue(std::move(action));
processedAny = true;
}
if (processedAny)
waitTimeout = activeTimeout;
}
}
bool TimerQueue::requestWorkers(unsigned count)
{
while (count > 0 && !inactiveWorkers.empty())
{
TimerWorker* worker = inactiveWorkers.begin()->second;
try
{
worker->start();
}
catch (...)
{
return false;
}
count -= 1;
inactiveWorkers.erase(inactiveWorkers.begin());
}
return true;
}
void TimerQueue::deactivateWorker(TimerWorker* worker)
{
std::unique_lock<Mutex> lock(mutex);
inactiveWorkers[worker->getID()] = worker;
}

View File

@@ -0,0 +1,129 @@
#pragma once
#include <common/threading/Condition.h>
#include <common/threading/PThread.h>
#include <chrono>
#include <deque>
#include <functional>
#include <map>
class TimerQueue;
class TimerWorkList
{
public:
void enqueue(std::function<void ()> fn);
std::pair<bool, std::function<void ()>> deque(int timeoutMS);
size_t waitingItems() const;
private:
mutable Mutex mutex;
Condition condition;
std::deque<std::function<void ()>> available;
};
class TimerWorker : public PThread
{
public:
TimerWorker(TimerQueue& queue, TimerWorkList& workList, unsigned id, bool keepAlive);
~TimerWorker();
void run() override;
void start();
void stop();
bool isRunning() const
{
return state.read() == State_RUNNING;
}
unsigned getID() const { return id; }
private:
enum State {
State_STOPPED,
State_RUNNING,
State_STOPPING,
};
TimerQueue* queue;
TimerWorkList* workList;
bool keepAlive;
unsigned id;
AtomicSizeT state; // holds values from the enum State
};
class TimerQueue : public PThread
{
friend class TimerWorker;
private:
typedef std::chrono::steady_clock Clock;
typedef std::chrono::time_point<Clock> TimePoint;
typedef std::map<std::pair<TimePoint, uint64_t>, std::function<void ()>> QueueType;
// if the system's steady_clock is not at least 1ms accurate, bail now.
static_assert(
std::ratio_less_equal<Clock::period, std::milli>::value,
"System clock too coarse");
public:
class EntryHandle
{
friend class TimerQueue;
public:
void cancel()
{
queue->cancel(*this);
}
private:
EntryHandle(TimerQueue& queue, std::pair<TimePoint, uint64_t> key)
: queue(&queue), key(key)
{}
TimerQueue* queue;
std::pair<TimePoint, uint64_t> key;
};
TimerQueue(unsigned minPoolSize = 1, unsigned maxPoolSize = 100);
~TimerQueue();
// class is neither copyable nor movable for now, since we do not need that (yet)
TimerQueue(TimerQueue&&) = delete;
TimerQueue& operator=(TimerQueue&&) = delete;
EntryHandle enqueue(std::chrono::milliseconds timeout, std::function<void ()> action);
void cancel(const EntryHandle& handle);
void run() override;
private:
Mutex mutex;
Condition condition;
uint64_t entrySequence;
QueueType queue;
TimerWorkList workList;
// these have to be pointers instead of values because older libstdc++ versions do not have
// map::emplace.
std::map<unsigned, std::shared_ptr<TimerWorker>> workerPool;
std::map<unsigned, TimerWorker*> inactiveWorkers;
bool requestWorkers(unsigned count);
void deactivateWorker(TimerWorker* worker);
};

View File

@@ -0,0 +1,385 @@
#include <common/app/AbstractApp.h>
#include <common/components/worker/IncomingDataWork.h>
#include <common/components/streamlistenerv2/StreamListenerV2.h>
#include <common/toolkit/StringTk.h>
#include "ConnAcceptor.h"
#include <sys/epoll.h>
#define EPOLL_EVENTS_NUM (8) /* how many events we can take at once from epoll_wait */
ConnAcceptor::ConnAcceptor(AbstractApp* app, NicAddressList& localNicList,
unsigned short listenPort)
: PThread("ConnAccept"),
app(app),
log("ConnAccept"),
listenPort(listenPort),
localNicCapsUpdated(false)
{
NetworkInterfaceCard::supportedCapabilities(&localNicList, &localNicCaps);
int epollCreateSize = 10; // size "10" is just a hint (and is actually ignored since Linux 2.6.8)
this->epollFD = epoll_create(epollCreateSize);
if(epollFD == -1)
{
throw ComponentInitException(std::string("Error during epoll_create(): ") +
System::getErrString() );
}
if(!initSocks() )
throw ComponentInitException("Unable to initialize socket");
}
ConnAcceptor::~ConnAcceptor()
{
if(epollFD != -1)
close(epollFD);
SAFE_DELETE(tcpListenSock);
SAFE_DELETE(rdmaListenSock);
}
bool ConnAcceptor::startRDMASocket(NicListCapabilities* localNicCaps)
{
auto cfg = PThread::getCurrentThreadApp()->getCommonConfig();
if(localNicCaps->supportsRDMA)
{ // RDMA usage is enabled
try
{
rdmaListenSock = RDMASocket::create().release();
rdmaListenSock->setTimeouts(cfg->getConnRDMATimeoutConnect(),
cfg->getConnRDMATimeoutFlowSend(), cfg->getConnRDMATimeoutPoll());
rdmaListenSock->bind(listenPort);
rdmaListenSock->listen();
struct epoll_event epollEvent;
epollEvent.events = EPOLLIN;
epollEvent.data.ptr = rdmaListenSock;
if(epoll_ctl(epollFD, EPOLL_CTL_ADD, rdmaListenSock->getFD(), &epollEvent) == -1)
{
log.logErr(std::string("Unable to add RDMA listen sock to epoll set: ") +
System::getErrString() );
return false;
}
log.log(3, std::string("Listening for RDMA connections: Port ") +
StringTk::intToStr(listenPort) );
}
catch(SocketException& e)
{
log.logErr(std::string("RDMA socket: ") + e.what() );
SAFE_DELETE(rdmaListenSock);
return false;
}
}
return true;
}
bool ConnAcceptor::initSocks()
{
auto cfg = PThread::getCurrentThreadApp()->getCommonConfig();
rdmaListenSock = NULL;
tcpListenSock = NULL;
// RDMA
if (!startRDMASocket(&localNicCaps))
return false;
// TCP
try
{
tcpListenSock = new StandardSocket(PF_INET, SOCK_STREAM);
tcpListenSock->setSoReuseAddr(true);
int bufsize = cfg->getConnTCPRcvBufSize();
if (bufsize > 0)
tcpListenSock->setSoRcvBuf(bufsize);
tcpListenSock->bind(listenPort);
tcpListenSock->listen();
struct epoll_event epollEvent;
epollEvent.events = EPOLLIN;
epollEvent.data.ptr = tcpListenSock;
if(epoll_ctl(epollFD, EPOLL_CTL_ADD, tcpListenSock->getFD(), &epollEvent) == -1)
{
log.logErr(std::string("Unable to add TCP listen sock to epoll set: ") +
System::getErrString() );
return false;
}
log.log(Log_NOTICE, std::string("Listening for TCP connections: Port ") +
StringTk::intToStr(listenPort) );
}
catch(SocketException& e)
{
log.logErr(std::string("TCP socket: ") + e.what() );
SAFE_DELETE(tcpListenSock);
return false;
}
return true;
}
void ConnAcceptor::updateLocalNicList(NicAddressList& localNicList)
{
// we don't store the localNicList, just use it to update localNicCaps
NicListCapabilities localNicCaps;
NetworkInterfaceCard::supportedCapabilities(&localNicList, &localNicCaps);
const std::lock_guard<Mutex> lock(localNicCapsMutex);
this->localNicCaps = localNicCaps;
localNicCapsUpdated = true;
}
void ConnAcceptor::run()
{
try
{
registerSignalHandler();
listenLoop();
log.log(Log_DEBUG, "Component stopped.");
}
catch(std::exception& e)
{
PThread::getCurrentThreadApp()->handleComponentException(e);
}
}
void ConnAcceptor::handleNewLocalNicCaps()
{
NicListCapabilities localNicCaps;
bool localNicCapsUpdated;
{
const std::lock_guard<Mutex> lock(localNicCapsMutex);
localNicCapsUpdated = this->localNicCapsUpdated;
if (localNicCapsUpdated)
{
localNicCaps = this->localNicCaps;
this->localNicCapsUpdated = false;
}
}
if (localNicCapsUpdated)
{
if (localNicCaps.supportsRDMA)
{
if (!rdmaListenSock)
startRDMASocket(&localNicCaps);
}
else if (rdmaListenSock)
{
log.log(Log_NOTICE, std::string("Shutdown RDMA listen sock..."));
if(epoll_ctl(epollFD, EPOLL_CTL_DEL, rdmaListenSock->getFD(), NULL) == -1)
log.logErr(std::string("Unable to remove RDMA listen sock from epoll set: ") +
System::getErrString() );
SAFE_DELETE(rdmaListenSock);
log.log(Log_NOTICE, std::string("Shutdown RDMA listen sock complete"));
}
}
}
void ConnAcceptor::listenLoop()
{
const int epollTimeoutMS = 3000;
struct epoll_event epollEvents[EPOLL_EVENTS_NUM];
// (just to have these values on the stack...)
const int epollFD = this->epollFD;
RDMASocket* rdmaListenSock;
StandardSocket* tcpListenSock;
// wait for incoming events and handle them...
while(!getSelfTerminate() )
{
handleNewLocalNicCaps();
rdmaListenSock = this->rdmaListenSock;
tcpListenSock = this->tcpListenSock;
//log.log(Log_DEBUG, std::string("Before poll(). pollArrayLen: ") +
// StringTk::uintToStr(pollArrayLen) );
int epollRes = epoll_wait(epollFD, epollEvents, EPOLL_EVENTS_NUM, epollTimeoutMS);
if(unlikely(epollRes < 0) )
{ // error occurred
if(errno == EINTR) // ignore interruption, because the debugger causes this
continue;
log.logErr(std::string("Unrecoverable epoll_wait error: ") + System::getErrString() );
break;
}
// handle incoming connection attempts
for(size_t i=0; i < (size_t)epollRes; i++)
{
struct epoll_event* currentEvent = &epollEvents[i];
Pollable* currentPollable = (Pollable*)currentEvent->data.ptr;
//log.log(Log_DEBUG, std::string("Incoming data on FD: ") +
// StringTk::intToStr(pollArray[i].fd) ); // debug in
if(currentPollable == rdmaListenSock)
onIncomingRDMAConnection(rdmaListenSock);
else
if(currentPollable == tcpListenSock)
onIncomingStandardConnection(tcpListenSock);
else
{ // unknown connection => should never happen
log.log(Log_WARNING, "Should never happen: Ignoring event for unknown connection. "
"FD: " + StringTk::uintToStr(currentPollable->getFD() ) );
}
}
}
}
/**
* Accept the incoming connection and add new socket to StreamListenerV2 queue.
*
* Note: This is for standard sockets like TCP.
*/
void ConnAcceptor::onIncomingStandardConnection(StandardSocket* sock)
{
try
{
struct sockaddr_in peerAddr;
socklen_t peerAddrLen = sizeof(peerAddr);
StandardSocket* acceptedSock =
(StandardSocket*)sock->accept( (struct sockaddr*)&peerAddr, &peerAddrLen);
// (note: level Log_DEBUG to avoid spamming the log until we have log topics)
log.log(Log_DEBUG, std::string("Accepted new connection from ") +
Socket::endpointAddrToStr(&peerAddr) +
std::string(" [SockFD: ") + StringTk::intToStr(acceptedSock->getFD() ) +
std::string("]") );
applySocketOptions(acceptedSock);
// hand the socket over to a stream listener
StreamListenerV2* listener = app->getStreamListenerByFD(acceptedSock->getFD() );
StreamListenerV2::SockReturnPipeInfo returnInfo(
StreamListenerV2::SockPipeReturn_NEWCONN, acceptedSock);
listener->getSockReturnFD()->write(&returnInfo, sizeof(returnInfo) );
}
catch(SocketException& se)
{
log.logErr(std::string("Trying to continue after connection accept error: ") +
se.what() );
}
}
/**
* Accept the incoming RDMA connection and add new socket to StreamListenerV2 queue.
*/
void ConnAcceptor::onIncomingRDMAConnection(RDMASocket* sock)
{
auto cfg = PThread::getCurrentThreadApp()->getCommonConfig();
/* accept the incoming connection (and loop until no more delayed events are waiting on
this socket) */
// (Note: RDMASockets use this internally also to handle other kindes of events) */
/* loop: check whether this is a false alarm (i.e. an event that is handled internally by the
socket when we call accept() ), try to accept a new connection (if
available) and also check for further events after accept */
while(sock->checkDelayedEvents() )
{
try
{
struct sockaddr_in peerAddr;
socklen_t peerAddrLen = sizeof(peerAddr);
sock->setConnectionRejectionRate(cfg->getConnectionRejectionRate());
RDMASocket* acceptedSock =
(RDMASocket*)sock->accept( (struct sockaddr*)&peerAddr, &peerAddrLen);
// note: RDMASocket::accept() might return NULL (which is not an error)
if(!acceptedSock)
{
log.log(Log_DEBUG, "Ignoring an internal event on the listening RDMA socket");
continue;
}
acceptedSock->setTimeouts(cfg->getConnRDMATimeoutConnect(),
cfg->getConnRDMATimeoutFlowSend(), cfg->getConnRDMATimeoutPoll());
// (note: level Log_DEBUG to avoid spamming the log until we have log topics)
log.log(Log_DEBUG, std::string("Accepted new RDMA connection from ") +
Socket::endpointAddrToStr(&peerAddr) +
std::string(" [SockFD: ") + StringTk::intToStr(acceptedSock->getFD() ) +
std::string("]") );
// hand the socket over to a stream listener
StreamListenerV2* listener = app->getStreamListenerByFD(acceptedSock->getFD() );
StreamListenerV2::SockReturnPipeInfo returnInfo(
StreamListenerV2::SockPipeReturn_NEWCONN, acceptedSock);
listener->getSockReturnFD()->write(&returnInfo, sizeof(returnInfo) );
}
catch(SocketException& se)
{
log.logErr(std::string("Trying to continue after RDMA connection accept error: ") +
se.what() );
}
}
}
/**
* Apply special socket options to a new incoming standard socket connection.
*/
void ConnAcceptor::applySocketOptions(StandardSocket* sock)
{
LogContext log("ConnAcceptor (apply socket options)");
try
{
sock->setTcpCork(false);
}
catch(SocketException& e)
{
log.log(Log_NOTICE, "Failed to disable TcpCork. "
"FD: " + StringTk::uintToStr(sock->getFD() ) + ". "
"Msg: " + std::string(e.what() ) );
}
try
{
sock->setTcpNoDelay(true);
}
catch(SocketException& e)
{
log.log(Log_NOTICE, "Failed to enable TcpNoDelay. "
"FD: " + StringTk::uintToStr(sock->getFD() ) + ". "
"Msg: " + std::string(e.what() ) );
}
try
{
sock->setSoKeepAlive(true);
}
catch(SocketException& e)
{
log.log(Log_NOTICE, "Failed to enable SoKeepAlive. "
"FD: " + StringTk::uintToStr(sock->getFD() ) + ". "
"Msg: " + std::string(e.what() ) );
}
}

View File

@@ -0,0 +1,57 @@
#pragma once
#include <common/app/log/LogContext.h>
#include <common/components/worker/queue/MultiWorkQueue.h>
#include <common/components/ComponentInitException.h>
#include <common/net/sock/StandardSocket.h>
#include <common/net/sock/RDMASocket.h>
#include <common/net/message/NetMessage.h>
#include <common/nodes/Node.h>
#include <common/threading/PThread.h>
#include <common/toolkit/poll/PollList.h>
#include <common/toolkit/Pipe.h>
#include <common/Common.h>
class AbstractApp; // forward declaration
class ConnAcceptor : public PThread
{
public:
ConnAcceptor(AbstractApp* app, NicAddressList& localNicList, unsigned short listenPort);
virtual ~ConnAcceptor();
private:
AbstractApp* app;
LogContext log;
unsigned short listenPort;
StandardSocket* tcpListenSock;
RDMASocket* rdmaListenSock;
int epollFD;
NicListCapabilities localNicCaps;
bool localNicCapsUpdated;
Mutex localNicCapsMutex;
bool initSocks();
bool startRDMASocket(NicListCapabilities* localNicCaps);
void handleNewLocalNicCaps();
virtual void run();
void listenLoop();
void onIncomingStandardConnection(StandardSocket* sock);
void onIncomingRDMAConnection(RDMASocket* sock);
void applySocketOptions(StandardSocket* sock);
public:
void updateLocalNicList(NicAddressList& nicList);
// getters & setters
};

View File

@@ -0,0 +1,258 @@
#include <common/app/log/LogContext.h>
#include <common/app/AbstractApp.h>
#include <common/components/streamlistenerv2/StreamListenerV2.h>
#include <common/threading/PThread.h>
#include <common/net/message/NetMessage.h>
#include "IncomingPreprocessedMsgWork.h"
void IncomingPreprocessedMsgWork::process(char* bufIn, unsigned bufInLen,
char* bufOut, unsigned bufOutLen)
{
const char* logContextStr = "Work (process incoming msg)";
const int recvTimeoutMS = 5000;
unsigned numReceived = NETMSG_HEADER_LENGTH; // (header actually received by stream listener)
std::unique_ptr<NetMessage> msg;
try
{
// attach stats to sock (stream listener already received the msg header)
stats.incVals.netRecvBytes += NETMSG_HEADER_LENGTH;
sock->setStats(&stats);
// make sure msg length fits into our receive buffer
unsigned msgLength = msgHeader.msgLength;
unsigned msgPayloadLength = msgLength - numReceived;
if(unlikely(msgPayloadLength > bufInLen) )
{ // message too big
LogContext(logContextStr).log(Log_NOTICE,
std::string("Received a message that is too large. Disconnecting: ") +
sock->getPeername() );
sock->unsetStats();
invalidateConnection(sock);
return;
}
// receive the message payload
if(msgPayloadLength)
sock->recvExactT(bufIn, msgPayloadLength, 0, recvTimeoutMS);
// we got the complete message buffer => create msg object
AbstractApp* app = PThread::getCurrentThreadApp();
auto cfg = app->getCommonConfig();
auto netMessageFactory = app->getNetMessageFactory();
msg = netMessageFactory->createFromPreprocessedBuf(&msgHeader, bufIn, msgPayloadLength);
if(unlikely(msg->getMsgType() == NETMSGTYPE_Invalid) )
{ // message invalid
LogContext(logContextStr).log(Log_NOTICE,
std::string("Received an invalid message. Disconnecting: ") + sock->getPeername() );
sock->unsetStats();
invalidateConnection(sock);
return;
}
// process the received msg
bool processRes = false;
if(likely(!cfg->getConnAuthHash() ||
sock->getIsAuthenticated() ||
(msg->getMsgType() == NETMSGTYPE_AuthenticateChannel) ) )
{ // auth disabled or channel is auth'ed or this is an auth msg => process
NetMessage::ResponseContext rctx(NULL, sock, bufOut, bufOutLen, &stats);
LOG_DBG(COMMUNICATION, DEBUG, "Beginning message processing.", sock->getPeername(),
msg->getMsgTypeStr());
processRes = msg->processIncoming(rctx);
}
else
LogContext(logContextStr).log(Log_NOTICE,
std::string("Rejecting message from unauthenticated peer: ") + sock->getPeername() );
// processing completed => cleanup
bool needSockRelease = msg->getReleaseSockAfterProcessing();
msg.reset();
if(!needSockRelease)
return; // sock release was already done within msg->processIncoming() method
if(unlikely(!processRes) )
{ // processIncoming encountered messaging error => invalidate connection
LogContext(logContextStr).log(Log_NOTICE,
std::string("Problem encountered during processing of a message. Disconnecting: ") +
sock->getPeername() );
invalidateConnection(sock);
return;
}
releaseSocket(app, &sock, NULL);
return;
}
catch(SocketTimeoutException& e)
{
LogContext(logContextStr).log(Log_NOTICE,
std::string("Connection timed out: ") + sock->getPeername() );
}
catch(SocketDisconnectException& e)
{
// (note: level Log_DEBUG here to avoid spamming the log until we have log topics)
LogContext(logContextStr).log(Log_DEBUG, std::string(e.what() ) );
}
catch(SocketException& e)
{
LogContext(logContextStr).log(Log_NOTICE,
std::string("Connection error: ") + sock->getPeername() + std::string(": ") +
std::string(e.what() ) );
}
// socket exception occurred => cleanup
if(msg && msg->getReleaseSockAfterProcessing() )
{
sock->unsetStats();
invalidateConnection(sock);
}
}
/**
* Release a valid incoming socket by returning it to the StreamListenerV2.
*
* Intended to be called
* a) either directly from within a msg->processIncoming() method (e.g. after sending an early
* response to a client)
* b) or by IncomingPreprocessedMsgWork::process() for cleanup after msg->processIncoming()
* returned true.
*
* @param sock pointer will be set to NULL to avoid accidental usage by caller after calling this
* method.
* @param msg may be NULL; if provided, msg->setReleaseSockAfterProcessing(false) will be called.
*/
void IncomingPreprocessedMsgWork::releaseSocket(AbstractApp* app, Socket** sock, NetMessage* msg)
{
Socket* sockCopy = *sock; // copy of sock pointer, so that we can set caller's sock to NULL
*sock = NULL;
if(msg)
msg->setReleaseSockAfterProcessing(false);
sockCopy->unsetStats();
// check for immediate data on rdma sockets
bool gotImmediateDataAvailable = checkRDMASocketImmediateData(app, sockCopy);
if(gotImmediateDataAvailable)
{ // immediate data available
// (socket was returned to stream listener by checkRDMASocketImmediateData() )
return;
}
// no immediate data available => return the socket to a stream listener
StreamListenerV2* listener = app->getStreamListenerByFD(sockCopy->getFD() );
StreamListenerV2::SockReturnPipeInfo returnInfo(
StreamListenerV2::SockPipeReturn_MSGDONE_NOIMMEDIATE, sockCopy);
listener->getSockReturnFD()->write(&returnInfo, sizeof(returnInfo) );
}
void IncomingPreprocessedMsgWork::invalidateConnection(Socket* sock)
{
const int recvTimeoutMS = 1;
try
{
sock->shutdownAndRecvDisconnect(recvTimeoutMS);
}
catch(SocketException& e)
{
// don't care, because the conn is invalid anyway
}
delete(sock);
}
/**
* Checks whether there is more immediate data available on an RDMASocket.
*
* If immediate data is available, we need to notify the stream listener about it.
* The reason for this is the fact that the filedescriptor will only send a notification
* in case of new incoming data, so we need to check for old incoming data (that has already
* been buffered internally by our RDMASocket impl) here.
*
* @return true if immediate data is available or if a conn error occurred (meaning the socket
* should not be returned to the streamListener by the caller)
*/
bool IncomingPreprocessedMsgWork::checkRDMASocketImmediateData(AbstractApp* app, Socket* sock)
{
const char* logContextStr = "Work (incoming data => check RDMA immediate data)";
// the immediate data check only applies to RDMA sockets
if(sock->getSockType() != NICADDRTYPE_RDMA)
return false;
// we got a RDMASocket
try
{
RDMASocket* rdmaSock = (RDMASocket*)sock;
if(!rdmaSock->nonblockingRecvCheck() )
return false; // no more data available at the moment
// we have immediate data available => inform the stream listener
LOG_DEBUG(logContextStr, Log_SPAM,
std::string("Got immediate data: ") + sock->getPeername() );
StreamListenerV2* listener = app->getStreamListenerByFD(sock->getFD() );
StreamListenerV2::SockReturnPipeInfo returnInfo(
StreamListenerV2::SockPipeReturn_MSGDONE_WITHIMMEDIATE, sock);
listener->getSockReturnFD()->write(&returnInfo, sizeof(returnInfo) );
return true;
}
catch(SocketDisconnectException& e)
{
LogContext(logContextStr).log(Log_DEBUG, std::string(e.what() ) );
}
catch(SocketException& e)
{
LogContext(logContextStr).log(Log_NOTICE,
std::string("Connection error: ") + sock->getPeername() + std::string(": ") +
std::string(e.what() ) );
}
// conn error occurred
delete(sock);
return true;
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include <common/app/AbstractApp.h>
#include <common/components/worker/Work.h>
#include <common/net/message/NetMessage.h>
#include <common/net/sock/Socket.h>
#include <common/Common.h>
class IncomingPreprocessedMsgWork : public Work
{
public:
/**
* Note: Be aware that this class is only for stream connections that need to be returned
* to a StreamListenerV2 after processing.
*
* @param msgHeader contents will be copied
*/
IncomingPreprocessedMsgWork(AbstractApp* app, Socket* sock, NetMessageHeader* msgHeader)
{
this->app = app;
this->sock = sock;
this->msgHeader = *msgHeader;
}
virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen);
static void releaseSocket(AbstractApp* app, Socket** sock, NetMessage* msg);
static void invalidateConnection(Socket* sock);
static bool checkRDMASocketImmediateData(AbstractApp* app, Socket* sock);
private:
AbstractApp* app;
Socket* sock;
NetMessageHeader msgHeader;
};

View File

@@ -0,0 +1,496 @@
#include <common/app/AbstractApp.h>
#include <common/components/streamlistenerv2/IncomingPreprocessedMsgWork.h>
#include <common/toolkit/StringTk.h>
#include "StreamListenerV2.h"
#include <sys/epoll.h>
#define EPOLL_EVENTS_NUM (512) /* make it big to avoid starvation of higher FDs */
#define RDMA_CHECK_FORCE_POLLLOOPS (7200) /* to avoid calling the check routine in each loop */
#define RDMA_CHECK_INTERVAL_MS (150*60*1000) /* 150mins (must be more than double of the
client-side idle disconnect interval to avoid cases where
server disconnects first) */
#define SOCKRETURN_SOCKS_NUM (32)
StreamListenerV2::StreamListenerV2(const std::string& listenerID, AbstractApp* app,
StreamListenerWorkQueue* workQueue)
: PThread(listenerID),
app(app),
log("StreamLisV2"),
workQueue(workQueue),
rdmaCheckForceCounter(0),
useAggressivePoll(false)
{
int epollCreateSize = 10; // size "10" is just a hint (and is actually ignored since Linux 2.6.8)
this->epollFD = epoll_create(epollCreateSize);
if(epollFD == -1)
{
throw ComponentInitException(std::string("Error during epoll_create(): ") +
System::getErrString() );
}
if(!initSockReturnPipe() )
throw ComponentInitException("Unable to initialize sock return pipe");
}
StreamListenerV2::~StreamListenerV2()
{
delete(sockReturnPipe);
deleteAllConns();
if(epollFD != -1)
close(epollFD);
}
bool StreamListenerV2::initSockReturnPipe()
{
this->sockReturnPipe = new Pipe(false, true);
struct epoll_event epollEvent;
epollEvent.events = EPOLLIN;
epollEvent.data.ptr = sockReturnPipe->getReadFD();
if(epoll_ctl(epollFD, EPOLL_CTL_ADD, sockReturnPipe->getReadFD()->getFD(), &epollEvent) == -1)
{
log.logErr(std::string("Unable to add sock return pipe (read side) to epoll set: ") +
System::getErrString() );
return false;
}
return true;
}
void StreamListenerV2::run()
{
try
{
registerSignalHandler();
listenLoop();
log.log(Log_DEBUG, "Component stopped.");
}
catch(std::exception& e)
{
PThread::getCurrentThreadApp()->handleComponentException(e);
}
}
void StreamListenerV2::listenLoop()
{
const int epollTimeoutMS = useAggressivePoll ? 0 : 3000;
struct epoll_event epollEvents[EPOLL_EVENTS_NUM];
// (just to have these values on the stack...)
const int epollFD = this->epollFD;
FileDescriptor* sockReturnPipeReadEnd = this->sockReturnPipe->getReadFD();
bool runRDMAConnIdleCheck = false; // true just means we call the method (not enforce the check)
// wait for incoming events and handle them...
while(!getSelfTerminate() )
{
//log.log(Log_DEBUG, std::string("Before poll(). pollArrayLen: ") +
// StringTk::uintToStr(pollArrayLen) );
int epollRes = epoll_wait(epollFD, epollEvents, EPOLL_EVENTS_NUM, epollTimeoutMS);
if(unlikely(epollRes < 0) )
{ // error occurred
if(errno == EINTR) // ignore interruption, because the debugger causes this
continue;
log.logErr(std::string("Unrecoverable epoll_wait error: ") + System::getErrString() );
break;
}
else
if(unlikely(!epollRes || (rdmaCheckForceCounter++ > RDMA_CHECK_FORCE_POLLLOOPS) ) )
{ // epollRes==0 is nothing to worry about, just idle
// note: we can't run idle check here directly because the check might modify the
// poll set, which will be accessed in the loop below
runRDMAConnIdleCheck = true;
}
// handle incoming data & connection attempts
for(size_t i=0; i < (size_t)epollRes; i++)
{
struct epoll_event* currentEvent = &epollEvents[i];
Pollable* currentPollable = (Pollable*)currentEvent->data.ptr;
if(currentPollable == sockReturnPipeReadEnd)
onSockReturn();
else
onIncomingData( (Socket*)currentPollable);
}
if(unlikely(runRDMAConnIdleCheck) )
{ // note: whether check actually happens depends on elapsed time since last check
runRDMAConnIdleCheck = false;
rdmaConnIdleCheck();
}
}
}
/**
* Receive msg header and add the socket to the work queue.
*/
void StreamListenerV2::onIncomingData(Socket* sock)
{
// check whether this is just a false alarm from a RDMASocket
if( (sock->getSockType() == NICADDRTYPE_RDMA) &&
isFalseAlarm( (RDMASocket*)sock) )
{
return;
}
try
{
const int recvTimeoutMS = 5000;
char msgHeaderBuf[NETMSG_HEADER_LENGTH];
NetMessageHeader msgHeader;
// receive & deserialize message header
sock->recvExactT(msgHeaderBuf, NETMSG_HEADER_LENGTH, 0, recvTimeoutMS);
NetMessage::deserializeHeader(msgHeaderBuf, NETMSG_HEADER_LENGTH, &msgHeader);
/* (note on header verification: we leave header verification work to the worker threads to
save CPU cycles in the stream listener and instead just take what we need to know here, no
matter whether the header is valid or not.) */
// create work and add it to queue
//log.log(Log_DEBUG, "Creating new work for to the queue");
IncomingPreprocessedMsgWork* work = new IncomingPreprocessedMsgWork(app, sock, &msgHeader);
int sockFD = sock->getFD(); /* note: we store this here for delayed pollList removal, because
worker thread might disconnect, so the sock gets deleted by the worker and thus "sock->"
pointer becomes invalid */
sock->setHasActivity(); // mark sock as active (for idle disconnect check)
// (note: userID intToStr (not uint) because default userID (~0) looks better this way)
LOG_DEBUG("StreamListenerV2::onIncomingData", Log_DEBUG,
"Incoming message: " + netMessageTypeToStr(msgHeader.msgType) + "; "
"from: " + sock->getPeername() + "; "
"userID: " + StringTk::intToStr(msgHeader.msgUserID) +
(msgHeader.msgTargetID
? "; targetID: " + StringTk::uintToStr(msgHeader.msgTargetID)
: "") );
if (sock->getIsDirect())
getWorkQueue(msgHeader.msgTargetID)->addDirectWork(work, msgHeader.msgUserID);
else
getWorkQueue(msgHeader.msgTargetID)->addIndirectWork(work, msgHeader.msgUserID);
/* notes on sock handling:
*) no need to remove sock from epoll set, because we use edge-triggered mode with
oneshot flag (which disables further events after first one has been reported).
*) a sock that is closed by a worker is not a problem, because it will automatically be
removed from the epoll set by the kernel.
*) we just need to re-arm the epoll entry upon sock return. */
pollList.removeByFD(sockFD);
return;
}
catch(SocketTimeoutException& e)
{
log.log(Log_NOTICE, "Connection timed out: " + sock->getPeername() );
}
catch(SocketDisconnectException& e)
{
// (note: level Log_DEBUG here to avoid spamming the log until we have log topics)
log.log(Log_DEBUG, std::string(e.what() ) );
}
catch(SocketException& e)
{
log.log(Log_NOTICE,
"Connection error: " + sock->getPeername() + ": " + std::string(e.what() ) );
}
// socket exception occurred => cleanup
pollList.removeByFD(sock->getFD() );
IncomingPreprocessedMsgWork::invalidateConnection(sock); // also includes delete(sock)
}
/**
* Receive pointer to returned socket through the sockReturnPipe and re-add it to the pollList.
*/
void StreamListenerV2::onSockReturn()
{
SockReturnPipeInfo returnInfos[SOCKRETURN_SOCKS_NUM];
// try to get multiple returnInfos at once (if available)
ssize_t readRes = sockReturnPipe->getReadFD()->read(&returnInfos, sizeof(SockReturnPipeInfo) );
// loop: walk over each info and handle the contained socket
for(size_t i=0; ; i++)
{
SockReturnPipeInfo& currentReturnInfo = returnInfos[i];
// make sure we have a complete SockReturnPipeInfo
if(unlikely(readRes < (ssize_t)sizeof(SockReturnPipeInfo) ) )
{ // only got a partial SockReturnPipeInfo => recv the rest
char* currentReturnInfoChar = (char*)&currentReturnInfo;
sockReturnPipe->getReadFD()->readExact(
&currentReturnInfoChar[readRes], sizeof(SockReturnPipeInfo)-readRes);
readRes = sizeof(SockReturnPipeInfo);
}
// handle returnInfo depending on contained returnType...
Socket* currentSock = currentReturnInfo.sock;
SockPipeReturnType returnType = currentReturnInfo.returnType;
switch(returnType)
{
case SockPipeReturn_MSGDONE_NOIMMEDIATE:
{ // most likely case: worker is done with a msg and now returns the sock to the epoll set
struct epoll_event epollEvent;
epollEvent.events = EPOLLIN | EPOLLONESHOT | EPOLLET;
epollEvent.data.ptr = currentSock;
int epollRes = epoll_ctl(epollFD, EPOLL_CTL_MOD, currentSock->getFD(), &epollEvent);
if(likely(!epollRes) )
{ // sock was successfully re-armed in epoll set
pollList.add(currentSock);
break; // break out of switch
}
else
if(errno != ENOENT)
{ // error
log.logErr("Unable to re-arm sock in epoll set. "
"FD: " + StringTk::uintToStr(currentSock->getFD() ) + "; "
"SockTypeNum: " + StringTk::uintToStr(currentSock->getSockType() ) + "; "
"SysErr: " + System::getErrString() );
log.log(Log_NOTICE, "Disconnecting: " + currentSock->getPeername() );
delete(currentSock);
break; // break out of switch
}
/* for ENOENT, we fall through to NEWCONN, because this socket appearently wasn't
used with this stream listener yet, so we need to add it (instead of modify it) */
} // might fall through here on ENOENT
BEEGFS_FALLTHROUGH;
case SockPipeReturn_NEWCONN:
{ // new conn from ConnAcceptor (or wasn't used with this stream listener yet)
// add new socket file descriptor to epoll set
struct epoll_event epollEvent;
epollEvent.events = EPOLLIN | EPOLLONESHOT | EPOLLET;
epollEvent.data.ptr = currentSock;
int epollRes = epoll_ctl(epollFD, EPOLL_CTL_ADD, currentSock->getFD(), &epollEvent);
if(likely(!epollRes) )
{ // socket was successfully added to epoll set
pollList.add(currentSock);
}
else
{ // adding to epoll set failed => unrecoverable error
log.logErr("Unable to add sock to epoll set. "
"FD: " + StringTk::uintToStr(currentSock->getFD() ) + " "
"SockTypeNum: " + StringTk::uintToStr(currentSock->getSockType() ) + " "
"SysErr: " + System::getErrString() );
log.log(Log_NOTICE, "Disconnecting: " + currentSock->getPeername() );
delete(currentSock);
}
} break;
case SockPipeReturn_MSGDONE_WITHIMMEDIATE:
{ // special case: worker detected that immediate data is available after msg processing
// data immediately available => recv header and so on
onIncomingData(currentSock);
} break;
default:
{ // should never happen: unknown/unhandled returnType
log.logErr("Should never happen: "
"Unknown socket return type: " + StringTk::uintToStr(returnType) );
} break;
} // end of switch(returnType)
readRes -= sizeof(SockReturnPipeInfo);
if(!readRes)
break; // all received returnInfos have been processed
} // end of "for each received SockReturnPipeInfo" loop
}
/**
* Does not really check but instead just drops connections that have been idle for some time.
* Note: Actual checking is not performed to avoid timeout delays in the StreamListenerV2.
*/
void StreamListenerV2::rdmaConnIdleCheck()
{
const unsigned checkIntervalMS = RDMA_CHECK_INTERVAL_MS;
if(rdmaCheckT.elapsedMS() < checkIntervalMS)
{
this->rdmaCheckForceCounter = 0;
return;
}
int numCheckedConns = 0;
IntList disposalFDs;
PollMap* pollMap = pollList.getPollMap();
// walk over all rdma socks, check conn status, delete idle socks and add them to
// the disposal list (note: we do not modify the pollList/pollMap yet)
for(PollMapIter iter = pollMap->begin(); iter != pollMap->end(); iter++)
{
Socket* currentSock = (Socket*)iter->second;
if(currentSock->getSockType() != NICADDRTYPE_RDMA)
continue;
numCheckedConns++;
// rdma socket => check activity
if(currentSock->getHasActivity() )
{ // conn had activity since last check => reset activity flag
currentSock->resetHasActivity();
}
else
{ // conn was inactive the whole time => disconnect and add to disposal list
log.log(Log_DEBUG,
"Disconnecting idle RDMA connection: " + currentSock->getPeername() );
delete(currentSock);
disposalFDs.push_back(iter->first);
}
}
// remove idle socks from pollMap
for(IntListIter iter = disposalFDs.begin(); iter != disposalFDs.end(); iter++)
{
pollList.removeByFD(*iter);
}
if (!disposalFDs.empty())
{ // logging
LOG_DEBUG("StreamListenerV2::rdmaConnIdleCheck", Log_SPAM, std::string("Check results: ") +
std::string("disposed ") + StringTk::int64ToStr(disposalFDs.size() ) + "/" +
StringTk::int64ToStr(numCheckedConns) );
IGNORE_UNUSED_VARIABLE(numCheckedConns);
}
// reset time counter
rdmaCheckT.setToNow();
}
/**
* RDMA sockets might signal incoming events that are not actually incoming data, but instead
* handled internally in the IBVSocket class.
* In the latter case, we should not call recv() on the socket, because that would hang. So we need
* this method to decide wheter we put this socket into the incoming data queue or not.
*
* Note: A special case is a connection error that is detected during the false alarm
* check. We return true here, because the disconnect is handled internally so that
* no further steps are required by the caller.
*
* @return true in case of a false alarm (which means that no incoming data for recv is
* immediately available) or in case of an error; false otherwise (i.e. the caller can call recv()
* on this socket without blocking)
*/
bool StreamListenerV2::isFalseAlarm(RDMASocket* sock)
{
const char* logContext = "StreamListenerV2 (incoming RDMA check)";
try
{
if(!sock->nonblockingRecvCheck() )
{ // false alarm
LOG_DEBUG(logContext, Log_SPAM,
std::string("Ignoring false alarm from: ") + sock->getPeername() );
// we use one-shot mechanism, so we need to re-arm the socket after an event notification
struct epoll_event epollEvent;
epollEvent.events = EPOLLIN | EPOLLONESHOT | EPOLLET;
epollEvent.data.ptr = sock;
int epollRes = epoll_ctl(this->epollFD, EPOLL_CTL_MOD, sock->getFD(), &epollEvent);
if(unlikely(epollRes == -1) )
{ // error
log.logErr("Unable to re-arm socket in epoll set: " + System::getErrString() );
log.log(Log_NOTICE, "Disconnecting: " + sock->getPeername() );
delete(sock);
}
return true;
}
// we really have incoming data
return false;
}
catch(SocketDisconnectException& e)
{ // a disconnect here is nothing to worry about
LogContext(logContext).log(Log_DEBUG,
std::string(e.what() ) );
}
catch(SocketException& e)
{
LogContext(logContext).log(Log_NOTICE,
std::string("Connection error: ") + sock->getPeername() + std::string(": ") +
std::string(e.what() ) );
}
// conn error occurred
pollList.remove(sock);
delete(sock);
return true;
}
void StreamListenerV2::deleteAllConns()
{
PollMap* pollMap = pollList.getPollMap();
for(PollMapIter iter = pollMap->begin(); iter != pollMap->end(); iter++)
{
delete(iter->second);
}
}

View File

@@ -0,0 +1,117 @@
#pragma once
#include <common/app/log/LogContext.h>
#include <common/components/worker/queue/StreamListenerWorkQueue.h>
#include <common/components/ComponentInitException.h>
#include <common/net/sock/StandardSocket.h>
#include <common/net/sock/RDMASocket.h>
#include <common/net/message/NetMessage.h>
#include <common/nodes/Node.h>
#include <common/threading/PThread.h>
#include <common/toolkit/poll/PollList.h>
#include <common/toolkit/Pipe.h>
#include <common/Common.h>
class AbstractApp; // forward declaration
class StreamListenerV2 : public PThread
{
public:
// type definitions...
enum SockPipeReturnType
{
SockPipeReturn_NEWCONN = 0, /* a new connection from ConnAcceptor */
SockPipeReturn_MSGDONE_NOIMMEDIATE = 1, /* returned from msg worker, no immediate data */
SockPipeReturn_MSGDONE_WITHIMMEDIATE = 2, /* from worker with immediate data available */
};
/**
* This is what we will send over the socket return pipe
*/
struct SockReturnPipeInfo
{
/**
* Standard constructor for creating/sending a returnInfo.
*/
SockReturnPipeInfo(SockPipeReturnType returnType, Socket* sock) :
returnType(returnType), sock(sock) {}
/**
* For receiving only (no initialization of members).
*/
SockReturnPipeInfo() {}
SockPipeReturnType returnType;
Socket* sock;
};
public:
StreamListenerV2(const std::string& listenerID, AbstractApp* app,
StreamListenerWorkQueue* workQueue);
virtual ~StreamListenerV2();
private:
AbstractApp* app;
LogContext log;
StreamListenerWorkQueue* workQueue; // may be NULL in derived classes with own getWorkQueue()
int epollFD;
PollList pollList;
Pipe* sockReturnPipe;
Time rdmaCheckT;
int rdmaCheckForceCounter;
bool useAggressivePoll; // true to not sleep on epoll and burn CPU
bool initSockReturnPipe();
bool initSocks(unsigned short listenPort, NicListCapabilities* localNicCaps);
virtual void run();
void listenLoop();
void onIncomingData(Socket* sock);
void onSockReturn();
void rdmaConnIdleCheck();
bool isFalseAlarm(RDMASocket* sock);
void deleteAllConns();
public:
// getters & setters
FileDescriptor* getSockReturnFD() const
{
return sockReturnPipe->getWriteFD();
}
/**
* Only effective when set before running this component.
*/
void setUseAggressivePoll()
{
this->useAggressivePoll = true;
}
protected:
// getters & setters
/**
* Note: This default implementation just ignores the given targetID. Derived classes will
* make use of it.
*/
virtual StreamListenerWorkQueue* getWorkQueue(uint16_t targetID) const
{
return this->workQueue;
}
};

View File

@@ -0,0 +1 @@
#include "IncAtomicWork.h"

View File

@@ -0,0 +1,33 @@
#pragma once
#include <common/app/log/LogContext.h>
#include <common/components/worker/Work.h>
#include <common/threading/Atomics.h>
template <class TemplateType>
class DecAtomicWork : public Work
{
public:
DecAtomicWork(Atomic<TemplateType>* atomicValue)
{
this->atomicValue = atomicValue;
}
virtual ~DecAtomicWork() { };
void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen)
{
LOG_DEBUG("DecAtomicWork", Log_DEBUG,
"Processing DecAtomicWork");
// increment counter
this->atomicValue->decrease();
LOG_DEBUG("DecAtomicWork", Log_DEBUG,
"Processed DecAtomicWork");
}
private:
Atomic<TemplateType>* atomicValue;
};

View File

@@ -0,0 +1,14 @@
#pragma once
#include "Work.h"
class DummyWork : public Work
{
public:
virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen)
{
// nothing to be done here
}
};

View File

@@ -0,0 +1,208 @@
#include "GetQuotaInfoWork.h"
#include <common/net/message/storage/quota/GetQuotaInfoRespMsg.h>
#include <common/toolkit/MessagingTk.h>
#include <mutex>
/**
* merges a given list into the map of this worker
*
* @param inList the list with the QuotaData to merge with the QuotaDataMap of this worker
*/
void GetQuotaInfoWork::mergeOrInsertNewQuotaData(QuotaDataList* inList,
QuotaInodeSupport inQuotaInodeSupport)
{
{
const std::lock_guard<Mutex> lock(*quotaResultsMutex);
mergeQuotaInodeSupportUnlocked(inQuotaInodeSupport);
for(QuotaDataListIter inListIter = inList->begin(); inListIter != inList->end(); inListIter++)
{
QuotaDataMapIter outIter = this->quotaResults->find(inListIter->getID() );
if(outIter != this->quotaResults->end() )
{
if(outIter->second.mergeQuotaDataCounter(&(*inListIter) ) )
{ // success
*this->result = 0;
}
else
{ // insert failed, error value 1 or TargetNumID depends on target selection mode
if(this->cfg.cfgTargetSelection != GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST)
*this->result = this->cfg.cfgTargetNumID;
else
*this->result = 1;
}
}
else
{
if(this->quotaResults->insert(QuotaDataMapVal(inListIter->getID(), *inListIter) ).second)
{ // success
*this->result = 0;
}
else
{ // insert failed, error value 1 or TargetNumID depends on target selection mode
if(this->cfg.cfgTargetSelection != GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST)
*this->result = this->cfg.cfgTargetNumID;
else
*this->result = 1;
}
}
}
}
if(*this->result != 0)
{
LogContext("GetQuotaInfo").log(Log_WARNING, "Invalid QuotaData from target: " +
StringTk::uintToStr(this->cfg.cfgTargetNumID));
}
}
void GetQuotaInfoWork::process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen)
{
GetQuotaInfoMsg msg(this->cfg.cfgType, this->storagePoolId);
prepareMessage(this->messageNumber, &msg);
GetQuotaInfoRespMsg* respMsgCast;
const auto respMsg = MessagingTk::requestResponse(*storageNode, msg,
NETMSGTYPE_GetQuotaInfoResp);
if (!respMsg)
{
// error value 1 or TargetNumID depends on target selection mode
if(this->cfg.cfgTargetSelection != GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST)
*this->result = this->cfg.cfgTargetNumID;
else
*this->result = 1;
this->counter->incCount();
return;
}
respMsgCast = (GetQuotaInfoRespMsg*)respMsg.get();
//merge the QuotaData from the response with the existing quotaData
mergeOrInsertNewQuotaData(&respMsgCast->getQuotaDataList(), respMsgCast->getQuotaInodeSupport() );
this->counter->incCount();
}
/*
* type dependent message preparations
*
* @param messageNumber the sequence number of the message
* @param msg the message to send
*/
void GetQuotaInfoWork::prepareMessage(int messageNumber, GetQuotaInfoMsg* msg)
{
if(this->cfg.cfgTargetSelection == GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST)
msg->setTargetSelectionAllTargetsOneRequestForAllTargets();
else
if(this->cfg.cfgTargetSelection == GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST_PER_TARGET)
msg->setTargetSelectionAllTargetsOneRequestPerTarget(this->cfg.cfgTargetNumID);
else
if(this->cfg.cfgTargetSelection == GETQUOTACONFIG_SINGLE_TARGET)
msg->setTargetSelectionSingleTarget(this->cfg.cfgTargetNumID);
if(this->cfg.cfgUseList || this->cfg.cfgUseAll)
{
msg->setQueryType(GetQuotaInfoMsg::QUERY_TYPE_ID_LIST);
getIDsFromListForMessage(messageNumber, msg->getIDList());
}
else
if(this->cfg.cfgUseRange)
{
unsigned startRange, endRange;
getIDRangeForMessage(messageNumber, startRange, endRange);
msg->setIDRange(startRange, endRange);
}
else
{
msg->setID(this->cfg.cfgID);
}
}
/*
* calculates the first ID and the last ID from the ID range, which fits into the payload of the msg
*
* @param messageNumber the sequence number of the message
* @param outFirstID the first ID of the range for the message with the given sequence number
* @param outLastID the last ID of the range for the message with the given sequence number
*/
void GetQuotaInfoWork::getIDRangeForMessage(int messageNumber, unsigned &outFirstID,
unsigned &outLastID)
{
if( (this->cfg.cfgIDRangeEnd - this->cfg.cfgIDRangeStart) <= GETQUOTAINFORESPMSG_MAX_ID_COUNT)
{
outFirstID = this->cfg.cfgIDRangeStart;
outLastID = this->cfg.cfgIDRangeEnd;
}
else
{
unsigned startThisMessage = this->cfg.cfgIDRangeStart +
(messageNumber * GETQUOTAINFORESPMSG_MAX_ID_COUNT);
unsigned startNextMessage = this->cfg.cfgIDRangeStart +
( (messageNumber + 1) * GETQUOTAINFORESPMSG_MAX_ID_COUNT);
outFirstID = startThisMessage;
outLastID = startNextMessage - 1;
if(outLastID > this->cfg.cfgIDRangeEnd)
outLastID = this->cfg.cfgIDRangeEnd;
}
}
/*
* calculates the sublist of ID list, which fits into the payload of the msg
*
* @param messageNumber the sequence number of the message
* @param outList the ID list for the message with the given sequence number
*/
void GetQuotaInfoWork::getIDsFromListForMessage(int messageNumber, UIntList* outList)
{
int counter = 0;
int startThisMessage = (messageNumber * GETQUOTAINFORESPMSG_MAX_ID_COUNT);
int startNextMessage = ( (messageNumber + 1) * GETQUOTAINFORESPMSG_MAX_ID_COUNT);
for(UIntListIter iter = this->cfg.cfgIDList.begin(); iter != this->cfg.cfgIDList.end(); iter++)
{
if(counter < startThisMessage)
{
counter++;
continue;
}
else
if(counter >= startNextMessage)
return;
counter++;
outList->push_back(*iter);
}
}
void GetQuotaInfoWork::mergeQuotaInodeSupportUnlocked(QuotaInodeSupport inQuotaInodeSupport)
{
if(*quotaInodeSupport == QuotaInodeSupport_UNKNOWN)
{ // if the current state is QuotaInodeSupport_UNKNOWN, the input value is the new state
*quotaInodeSupport = inQuotaInodeSupport;
return;
}
if(inQuotaInodeSupport == QuotaInodeSupport_ALL_BLOCKDEVICES)
{
if(*quotaInodeSupport == QuotaInodeSupport_NO_BLOCKDEVICES)
{
*quotaInodeSupport = QuotaInodeSupport_SOME_BLOCKDEVICES;
}
}
else if(inQuotaInodeSupport == QuotaInodeSupport_NO_BLOCKDEVICES)
{
if(*quotaInodeSupport == QuotaInodeSupport_ALL_BLOCKDEVICES)
{
*quotaInodeSupport = QuotaInodeSupport_SOME_BLOCKDEVICES;
}
}
// we ignore QuotaInodeSupport_SOME_BLOCKDEVICES because this state couldn't be changed
// we also ignore QuotaInodeSupport_UNKNOWN because it can not change the state
}

View File

@@ -0,0 +1,67 @@
#pragma once
#include <common/Common.h>
#include <common/net/message/storage/quota/GetQuotaInfoMsg.h>
#include <common/nodes/Node.h>
#include <common/storage/quota/Quota.h>
#include <common/storage/quota/QuotaData.h>
#include <common/storage/quota/GetQuotaInfo.h>
#include <common/toolkit/SynchronizedCounter.h>
#include "Work.h"
class GetQuotaInfoWork: public Work
{
public:
/*
* Constructor for thread-safe use of the result map
*
*/
GetQuotaInfoWork(GetQuotaInfoConfig cfg, NodeHandle storageNode, int messageNumber,
QuotaDataMap* outQuotaResults, Mutex* quotaResultsMutex,
SynchronizedCounter *counter, uint16_t* result, QuotaInodeSupport* quotaInodeSupport,
StoragePoolId storagePoolId)
{
this->cfg = cfg;
this->storageNode = storageNode;
this->messageNumber = messageNumber;
this->quotaResults = outQuotaResults;
this->quotaResultsMutex = quotaResultsMutex;
this->quotaInodeSupport = quotaInodeSupport;
this->counter = counter;
this->result = result;
this->storagePoolId = storagePoolId;
}
virtual ~GetQuotaInfoWork()
{
}
virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen);
private:
GetQuotaInfoConfig cfg; // configuration qith all information to query the quota data
NodeHandle storageNode; // the node query
int messageNumber; // the message number which is processed by this work
QuotaDataMap* quotaResults; // the quota data from the server after requesting the server
QuotaInodeSupport* quotaInodeSupport; // the support level for inode quota of the blockdevice
Mutex* quotaResultsMutex; // synchronize quotaResults and quotaInodeSupport
SynchronizedCounter* counter; // counter for finished worker
uint16_t* result; // result of the worker, 0 if success, if error the TargetNumID
StoragePoolId storagePoolId; // only relevant for sending a GetQuotaMsg to the mgmtd
void prepareMessage(int messageNumber, GetQuotaInfoMsg* msg);
void getIDRangeForMessage(int messageNumber, unsigned &outFirstID, unsigned &outLastID);
void getIDsFromListForMessage(int messageNumber, UIntList* outList);
void mergeOrInsertNewQuotaData(QuotaDataList* inList, QuotaInodeSupport inQuotaInodeSupport);
void mergeQuotaInodeSupportUnlocked(QuotaInodeSupport inQuotaInodeSupport);
};

View File

@@ -0,0 +1 @@
#include "IncAtomicWork.h"

View File

@@ -0,0 +1,33 @@
#pragma once
#include <common/app/log/LogContext.h>
#include <common/components/worker/Work.h>
#include <common/threading/Atomics.h>
template <class TemplateType>
class IncAtomicWork : public Work
{
public:
IncAtomicWork(Atomic<TemplateType>* atomicValue)
{
this->atomicValue = atomicValue;
}
virtual ~IncAtomicWork() { };
void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen)
{
LOG_DEBUG("IncAtomicWork", Log_DEBUG,
"Processing IncAtomicWork");
// increment counter
this->atomicValue->increase();
LOG_DEBUG("IncAtomicWork", Log_DEBUG,
"Processed IncAtomicWork");
}
private:
Atomic<TemplateType>* atomicValue;
};

View File

@@ -0,0 +1,32 @@
#pragma once
#include <common/app/log/LogContext.h>
#include <common/components/worker/Work.h>
#include <common/toolkit/SynchronizedCounter.h>
class IncSyncedCounterWork : public Work
{
public:
IncSyncedCounterWork(SynchronizedCounter* counter)
{
this->counter = counter;
}
virtual ~IncSyncedCounterWork() { };
void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen)
{
LOG_DEBUG("IncSyncedCounterWork", Log_DEBUG,
"Processing IncSyncedCounterWork");
// increment counter
counter->incCount();
LOG_DEBUG("IncSyncedCounterWork", Log_DEBUG,
"Processed IncSyncedCounterWork");
}
private:
SynchronizedCounter* counter;
};

View File

@@ -0,0 +1,204 @@
#include <common/app/log/LogContext.h>
#include <common/app/AbstractApp.h>
#include <common/threading/PThread.h>
#include <common/net/message/NetMessage.h>
#include "IncomingDataWork.h"
void IncomingDataWork::process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen)
{
const char* logContextStr = "Work (process incoming data)";
const int recvTimeoutMS = 5000;
unsigned numReceived = 0;
sock->setStats(&stats);
try
{
// receive at least the message header
numReceived += sock->recvExactT(bufIn, NETMSG_MIN_LENGTH, 0, recvTimeoutMS);
unsigned msgLength = NetMessageHeader::extractMsgLengthFromBuf(bufIn, numReceived);
if(unlikely(msgLength > bufInLen) )
{ // message too big
LogContext(logContextStr).log(Log_NOTICE,
std::string("Received a message that is too large. Disconnecting: ") +
sock->getPeername() );
sock->unsetStats();
invalidateConnection(sock);
return;
}
// receive the rest of the message
if(msgLength > numReceived)
sock->recvExactT(&bufIn[numReceived], msgLength-numReceived, 0, recvTimeoutMS);
// we got the complete message buffer => create msg object
AbstractApp* app = PThread::getCurrentThreadApp();
auto cfg = app->getCommonConfig();
auto netMessageFactory = app->getNetMessageFactory();
auto msg = netMessageFactory->createFromRaw(bufIn, msgLength);
if(unlikely(msg->getMsgType() == NETMSGTYPE_Invalid) )
{ // message invalid
LogContext(logContextStr).log(Log_NOTICE,
std::string("Received an invalid message. Disconnecting: ") + sock->getPeername() );
sock->unsetStats();
invalidateConnection(sock);
return;
}
// process the received msg
bool processRes = false;
if(likely(!cfg->getConnAuthHash() ||
sock->getIsAuthenticated() ||
(msg->getMsgType() == NETMSGTYPE_AuthenticateChannel) ) )
{ // auth disabled or channel is auth'ed or this is an auth msg => process
NetMessage::ResponseContext rctx(NULL, sock, bufOut, bufOutLen, &stats);
processRes = msg->processIncoming(rctx);
}
else
LogContext(logContextStr).log(Log_NOTICE,
std::string("Rejecting message from unauthenticated peer: ") + sock->getPeername() );
// processing completed => cleanup
sock->unsetStats();
if(unlikely(!processRes) )
{
LogContext(logContextStr).log(Log_NOTICE,
std::string("Problem encountered during processing of a message. Disconnecting: ") +
sock->getPeername() );
invalidateConnection(sock);
return;
}
// processing completed successfully
if(checkRDMASocketImmediateData(streamListener, sock) )
{ // immediate data available => do not return the socket to the streamListener
// (new work has been added directly)
return;
}
// stream socket => return the socket to the StreamListener
streamListener->getSockReturnFD()->write(&sock, sizeof(Socket*) );
return;
}
catch(SocketTimeoutException& e)
{
LogContext(logContextStr).log(Log_NOTICE,
std::string("Connection timed out: ") + sock->getPeername() );
}
catch(SocketDisconnectException& e)
{
// (note: level Log_DEBUG to avoid spamming the log until we have log topics)
LogContext(logContextStr).log(Log_DEBUG, std::string(e.what() ) );
}
catch(SocketException& e)
{
LogContext(logContextStr).log(Log_NOTICE,
std::string("Connection error: ") + sock->getPeername() + std::string(": ") +
std::string(e.what() ) );
}
sock->unsetStats();
invalidateConnection(sock);
}
void IncomingDataWork::invalidateConnection(Socket* sock)
{
const int recvTimeoutMS = 500;
try
{
sock->shutdownAndRecvDisconnect(recvTimeoutMS);
}
catch(SocketException& e)
{
// don't care, because the conn is invalid anyway
}
delete(sock);
}
/**
* Checks whether there is more immediate data available on an RDMASocket.
*
* If immediate data is available, a new Work package will be added to the Queue.
* The reason for this is the fact that the filedescriptor will only send a notification
* in case of new incoming data, so we need to check for old incoming data (that has already
* been buffered internally by our impl) here.
*
* @return true if immediate data is available or if a conn error occurred (meaning the socket
* should not be returned to the streamListener)
*/
bool IncomingDataWork::checkRDMASocketImmediateData(StreamListener* streamListener, Socket* sock)
{
const char* logContextStr = "Work (incoming data => check RDMA immediate data)";
if(sock->getSockType() != NICADDRTYPE_RDMA)
return false;
// we have an RDMASocket
try
{
RDMASocket* rdmaSock = (RDMASocket*)sock;
if(!rdmaSock->nonblockingRecvCheck() )
{ // no more data available at the moment
LOG_DEBUG(logContextStr, Log_SPAM,
std::string("No immediate data: ") + sock->getPeername() );
return false;
}
// we have immediate data available => add to queue
LOG_DEBUG(logContextStr, Log_SPAM,
std::string("Incoming RDMA data from: ") + sock->getPeername() );
IncomingDataWork* work = new IncomingDataWork(streamListener, sock);
if(sock->getIsDirect() )
streamListener->getWorkQueue()->addDirectWork(work);
else
streamListener->getWorkQueue()->addIndirectWork(work);
return true;
}
catch(SocketDisconnectException& e)
{
LogContext(logContextStr).log(Log_DEBUG, std::string(e.what() ) );
}
catch(SocketException& e)
{
LogContext(logContextStr).log(Log_NOTICE,
std::string("Connection error: ") + sock->getPeername() + std::string(": ") +
std::string(e.what() ) );
}
// conn error occurred
delete(sock);
return true;
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include <common/components/worker/Work.h>
#include <common/components/StreamListener.h>
#include <common/net/sock/Socket.h>
class IncomingDataWork : public Work
{
public:
/**
* Note: Be aware that this is class is only for stream connections that need to be returned
* to the streamListener after processing.
*/
IncomingDataWork(StreamListener* streamListener, Socket* sock)
{
this->streamListener = streamListener;
this->sock = sock;
}
virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen);
static void invalidateConnection(Socket* sock);
static bool checkRDMASocketImmediateData(StreamListener* streamListener, Socket* sock);
private:
StreamListener* streamListener;
Socket* sock;
};

View File

@@ -0,0 +1,224 @@
#include <common/app/log/LogContext.h>
#include <common/app/AbstractApp.h>
#include <common/components/worker/Worker.h>
#include <common/net/message/NetMessage.h>
#include <common/threading/PThread.h>
#include "LocalConnWorker.h"
LocalConnWorker::LocalConnWorker(const std::string& workerID)
: UnixConnWorker(workerID, "LocalConnWorker"),
bufIn(NULL),
bufOut(NULL)
{
try
{
StandardSocket::createSocketPair(PF_UNIX, SOCK_STREAM, 0, &workerEndpoint, &clientEndpoint);
applySocketOptions(workerEndpoint);
applySocketOptions(clientEndpoint);
}
catch(SocketException& e)
{
throw ComponentInitException(e.what() );
}
}
LocalConnWorker::~LocalConnWorker()
{
SAFE_DELETE(workerEndpoint);
SAFE_DELETE(clientEndpoint);
SAFE_FREE(bufIn);
SAFE_FREE(bufOut);
}
void LocalConnWorker::run()
{
try
{
registerSignalHandler();
initBuffers();
workLoop();
log.log(4, "Component stopped.");
}
catch(std::exception& e)
{
PThread::getCurrentThreadApp()->handleComponentException(e);
}
}
void LocalConnWorker::workLoop()
{
log.log(4, std::string("Ready (TID: ") + StringTk::uint64ToStr(System::getTID() ) + ")");
try
{
while(!getSelfTerminate() )
{
//log.log(4, "Waiting for work...");
try
{
if(workerEndpoint->waitForIncomingData(5000) )
{
//log.log(4, "Got work");
bool processRes = processIncomingData(
bufIn, WORKER_BUFIN_SIZE, bufOut, WORKER_BUFOUT_SIZE);
if(!processRes)
{
break;
}
LOG_DEBUG("LocalConnWorker::workLoop", 4, "Work processed");
}
}
catch(SocketInterruptedPollException& e)
{
// ignore interruption, because the debugger causes this
}
} // end of while loop
}
catch(SocketException& e)
{
log.log(2, "Error occurred on the workerEndpoint socket: " + std::string(e.what() ) );
}
}
bool LocalConnWorker::processIncomingData(
char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen)
{
const int recvTimeoutMS = 5000;
unsigned numReceived = 0;
HighResolutionStats stats; // ignored currently
try
{
// receive at least the message header
numReceived += workerEndpoint->recvExactT(bufIn, NETMSG_MIN_LENGTH, 0, recvTimeoutMS);
unsigned msgLength = NetMessageHeader::extractMsgLengthFromBuf(bufIn, numReceived);
if(msgLength > bufInLen)
{ // message too big
LogContext("LocalConnWorker::processIncomingData").log(3,
std::string("Received a message that is too large. Disconnecting: ") +
workerEndpoint->getPeername() );
invalidateConnection();
return false;
}
// receive the rest of the message
if(msgLength > numReceived)
workerEndpoint->recvExactT(&bufIn[numReceived], msgLength-numReceived, 0, recvTimeoutMS);
// we got the complete message => deserialize and process it
auto msg = netMessageFactory->createFromRaw(bufIn, msgLength);
if(msg->getMsgType() == NETMSGTYPE_Invalid)
{ // message invalid
LogContext("LocalConnWorker::processIncomingData").log(3,
std::string("Received an invalid message. Disconnecting: ") +
workerEndpoint->getPeername() );
invalidateConnection();
return false;
}
NetMessage::ResponseContext rctx(NULL, workerEndpoint, bufOut, bufOutLen, &stats, true);
bool processRes = msg->processIncoming(rctx);
if(!processRes)
{
LogContext("LocalConnWorker::processIncomingData").log(3,
std::string("Problem encountered during processing of a message. Disconnecting: ") +
workerEndpoint->getPeername() );
invalidateConnection();
return false;
}
// completed successfully.
return true;
}
catch(SocketTimeoutException& e)
{
LogContext("LocalConnWorker::processIncomingData").log(3,
std::string("Connection timed out: ") + workerEndpoint->getPeername() );
}
catch(SocketDisconnectException& e)
{
LogContext("LocalConnWorker::processIncomingData").log(3, std::string(e.what() ) );
}
catch(SocketException& e)
{
LogContext("LocalConnWorker::processIncomingData").log(3,
std::string("Connection error: ") + workerEndpoint->getPeername() + std::string(": ") +
std::string(e.what() ) );
}
invalidateConnection();
return false;
}
void LocalConnWorker::invalidateConnection()
{
const int recvTimeoutMS = 2500;
try
{
workerEndpoint->shutdownAndRecvDisconnect(recvTimeoutMS);
}
catch(SocketException& e)
{
// don't care, because the conn is invalid anyway
}
}
void LocalConnWorker::applySocketOptions(StandardSocket* sock)
{
LogContext log("LocalConnWorker::applySocketOptions");
try
{
sock->setSoKeepAlive(true);
}
catch(SocketException& e)
{
log.log(3, "Failed to enable SoKeepAlive");
}
}
/**
* Note: For delayed buffer allocation during run(), because of NUMA.
*/
void LocalConnWorker::initBuffers()
{
void* bufInVoid = NULL;
void* bufOutVoid = NULL;
int inAllocRes = posix_memalign(&bufInVoid, sysconf(_SC_PAGESIZE), WORKER_BUFIN_SIZE);
int outAllocRes = posix_memalign(&bufOutVoid, sysconf(_SC_PAGESIZE), WORKER_BUFOUT_SIZE);
IGNORE_UNUSED_VARIABLE(inAllocRes);
IGNORE_UNUSED_VARIABLE(outAllocRes);
this->bufIn = (char*)bufInVoid;
this->bufOut = (char*)bufOutVoid;
}

View File

@@ -0,0 +1,48 @@
#pragma once
#include <common/components/ComponentInitException.h>
#include <common/components/worker/UnixConnWorker.h>
#include <common/threading/PThread.h>
#include <common/net/sock/StandardSocket.h>
/**
* Handles connections from the local machine to itself (to avoid deadlocks by using
* the standard work-queue mechanism).
*/
class LocalConnWorker : public UnixConnWorker
{
public:
/**
* Constructor for socket pair version.
*/
LocalConnWorker(const std::string& workerID);
virtual ~LocalConnWorker();
private:
char* bufIn;
char* bufOut;
StandardSocket* workerEndpoint;
StandardSocket* clientEndpoint;
virtual void run();
void workLoop();
bool processIncomingData(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen);
void invalidateConnection();
void applySocketOptions(StandardSocket* sock);
void initBuffers();
public:
// getters & setters
Socket* getClientEndpoint()
{
return clientEndpoint;
}
};

View File

@@ -0,0 +1,131 @@
#include <common/app/log/LogContext.h>
#include <common/app/AbstractApp.h>
#include <common/threading/PThread.h>
#include <common/net/message/NetMessage.h>
#include <common/toolkit/MessagingTk.h>
#include <common/nodes/NodeStoreServers.h>
#include <common/storage/StorageErrors.h>
#include <common/net/message/session/rw/ReadLocalFileV2Msg.h>
#include "ReadLocalFileV2Work.h"
void ReadLocalFileV2Work::process(
char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen)
{
TargetMapper* targetMapper = readInfo->targetMapper;
NodeStoreServers* nodes = readInfo->storageNodes;
FhgfsOpsErr resolveErr;
auto node = nodes->referenceNodeByTargetID(targetID, targetMapper, &resolveErr);
if (unlikely(!node))
{ // unable to resolve targetID
*nodeResult = -resolveErr;
}
else
{ // got node reference => begin communication
*nodeResult = communicate(*node, bufIn, bufInLen, bufOut, bufOutLen);
}
readInfo->counter->incCount();
}
int64_t ReadLocalFileV2Work::communicate(Node& node, char* bufIn, unsigned bufInLen,
char* bufOut, unsigned bufOutLen)
{
const char* logContext = "ReadFileV2 Work (communication)";
NodeConnPool* connPool = node.getConnPool();
NumNodeID localNodeNumID = readInfo->localNodeNumID;
AbstractApp* app = PThread::getCurrentThreadApp();
int connMsgLongTimeout = app->getCommonConfig()->getConnMsgLongTimeout();
int64_t retVal = -FhgfsOpsErr_COMMUNICATION;
int64_t numReceivedFileBytes = 0;
Socket* sock = NULL;
try
{
// connect
sock = connPool->acquireStreamSocket();
// prepare and send message
ReadLocalFileV2Msg readMsg(localNodeNumID, fileHandleID, targetID, pathInfoPtr,
accessFlags, offset, size);
if (this->firstWriteDoneForTarget)
readMsg.addMsgHeaderFeatureFlag(READLOCALFILEMSG_FLAG_SESSION_CHECK);
unsigned msgLength = readMsg.serializeMessage(bufOut, bufOutLen).second;
sock->send(bufOut, msgLength, 0);
// recv length info and file data loop
// note: we return directly from within this loop if the received header indicates an end of
// transmission
for( ; ; )
{
int64_t lengthInfo; // length info in fhgfs host byte order
sock->recvExactT(&lengthInfo, sizeof(lengthInfo), 0, connMsgLongTimeout);
lengthInfo = LE_TO_HOST_64(lengthInfo);
if(lengthInfo <= 0)
{ // end of file data transmission
if(unlikely(lengthInfo < 0) )
{ // error occurred
retVal = lengthInfo;
}
else
{ // normal end of file data transmission
retVal = numReceivedFileBytes;
}
connPool->releaseStreamSocket(sock);
return retVal;
}
// buffer overflow check
if(unlikely( (uint64_t)(numReceivedFileBytes + lengthInfo) > this->size) )
{
LogContext(logContext).logErr(
std::string("Received a lengthInfo that would lead to a buffer overflow from ") +
node.getAlias() + ": " + StringTk::int64ToStr(lengthInfo) );
retVal = -FhgfsOpsErr_INTERNAL;
break;
}
// positive result => node is going to send some file data
// receive announced dataPart
sock->recvExactT(&(this->buf)[numReceivedFileBytes], lengthInfo, 0, connMsgLongTimeout);
numReceivedFileBytes += lengthInfo;
} // end of recv file data + header loop
}
catch(SocketConnectException& e)
{
LogContext(logContext).log(2, std::string("Unable to connect to storage server: ") +
node.getAlias() );
retVal = -FhgfsOpsErr_COMMUNICATION;
}
catch(SocketException& e)
{
LogContext(logContext).logErr(
std::string("Communication error. SocketException: ") + e.what() );
LogContext(logContext).log(2, std::string("Sent request: handleID/offset/size: ") +
fileHandleID + "/" + StringTk::int64ToStr(offset) + "/" + StringTk::int64ToStr(size) );
retVal = -FhgfsOpsErr_COMMUNICATION;
}
// error clean up
if(sock)
connPool->invalidateStreamSocket(sock);
return retVal;
}

View File

@@ -0,0 +1,74 @@
#pragma once
#include <common/Common.h>
#include <common/net/sock/Socket.h>
#include <common/components/worker/Work.h>
#include <common/storage/PathInfo.h>
#include <common/toolkit/SynchronizedCounter.h>
#include <common/nodes/NumNodeID.h>
#include <common/nodes/TargetMapper.h>
#include <common/nodes/NodeStoreServers.h>
struct ReadLocalFileWorkInfo
{
ReadLocalFileWorkInfo(NumNodeID localNodeNumID, TargetMapper* targetMapper,
NodeStoreServers* storageNodes, SynchronizedCounter* counter) :
localNodeNumID(localNodeNumID), targetMapper(targetMapper), storageNodes(storageNodes),
counter(counter)
{
// all init done in initializer list
}
NumNodeID localNodeNumID;
TargetMapper* targetMapper;
NodeStoreServers* storageNodes;
SynchronizedCounter* counter;
};
class ReadLocalFileV2Work : public Work
{
public:
ReadLocalFileV2Work(const char* fileHandleID, char* buf, unsigned accessFlags, off_t offset,
size_t size, uint16_t targetID, PathInfo* pathInfoPtr, int64_t* nodeResult,
ReadLocalFileWorkInfo* readInfo, bool firstWriteDoneForTarget)
{
this->fileHandleID = fileHandleID;
this->buf = buf;
this->accessFlags = accessFlags;
this->offset = offset;
this->size = size;
this->targetID = targetID;
this->pathInfoPtr = pathInfoPtr;
this->nodeResult = nodeResult;
this->readInfo = readInfo;
this->firstWriteDoneForTarget = firstWriteDoneForTarget;
}
virtual ~ReadLocalFileV2Work()
{
}
virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen);
private:
const char* fileHandleID;
char* buf;
unsigned accessFlags;
off_t offset;
size_t size;
uint16_t targetID;
PathInfo* pathInfoPtr;
int64_t* nodeResult;
ReadLocalFileWorkInfo* readInfo;
bool firstWriteDoneForTarget; /* true if the first chunk was written to the storage target,
it's needed for the session check*/
int64_t communicate(Node& node, char* bufIn, unsigned bufInLen,
char* bufOut, unsigned bufOutLen);
};

View File

@@ -0,0 +1,52 @@
#pragma once
#include <common/components/ComponentInitException.h>
#include <common/net/sock/StandardSocket.h>
#include <common/threading/PThread.h>
class UnixConnWorker : public PThread
{
public:
UnixConnWorker(const std::string& workerID, std::string threadName)
: PThread(std::string(threadName) + workerID),
log(std::string(threadName) + workerID),
netMessageFactory(PThread::getCurrentThreadApp()->getNetMessageFactory() ),
available(false) {};
~UnixConnWorker() {};
virtual void run() = 0;
virtual void workLoop() = 0;
virtual bool processIncomingData(char* bufIn, unsigned bufInLen, char* bufOut,
unsigned bufOutLen) = 0;
virtual void invalidateConnection() = 0;
virtual void applySocketOptions(StandardSocket* sock) = 0;
virtual void initBuffers() = 0;
virtual Socket* getClientEndpoint() = 0;
protected:
LogContext log;
const AbstractNetMessageFactory* netMessageFactory;
bool available; // == !acquired
public:
// getters & setters
bool isAvailable()
{
return available;
}
void setAvailable(bool available)
{
this->available = available;
}
};

View File

@@ -0,0 +1,48 @@
#pragma once
#include <common/toolkit/HighResolutionStats.h>
#include <common/toolkit/TimeFine.h>
#include <common/Common.h>
class Work;
typedef std::list<Work*> WorkList;
typedef WorkList::iterator WorkListIter;
class Work
{
public:
Work()
{
HighResolutionStatsTk::resetStats(&stats);
}
virtual ~Work() {}
Work(const Work&) = delete;
Work(Work&&) = delete;
Work& operator=(const Work&) = delete;
Work& operator=(Work&&) = delete;
virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) = 0;
protected:
HighResolutionStats stats;
public:
HighResolutionStats* getHighResolutionStats()
{
return &stats;
}
#ifdef BEEGFS_DEBUG_PROFILING
TimeFine* getAgeTime()
{
return &age;
}
private:
TimeFine age;
#endif
};

View File

@@ -0,0 +1,147 @@
#include <common/toolkit/TimeFine.h>
#include "Worker.h"
Worker::Worker(const std::string& workerID, MultiWorkQueue* workQueue, QueueWorkType workType)
: PThread(workerID),
log(workerID),
terminateWithFullQueue(true),
bufInLen(WORKER_BUFIN_SIZE),
bufIn(NULL),
bufOutLen(WORKER_BUFOUT_SIZE),
bufOut(NULL),
workQueue(workQueue),
workType(workType),
personalWorkQueue(new PersonalWorkQueue() )
{
HighResolutionStatsTk::resetStats(&this->stats);
}
void Worker::run()
{
try
{
registerSignalHandler();
initBuffers();
/* note: we're not directly calling workLoop(workType) below, because:
1) we want to check that the given workType value is really valid.
2) we hope that the compiler can use this explicit check and value passing to optimize away
the if-condition inside waitForWorkByType (so that a worker does not need to check its
worktype for every incoming work). */
if(workType == QueueWorkType_DIRECT)
workLoop(QueueWorkType_DIRECT);
else
if(workType == QueueWorkType_INDIRECT)
workLoop(QueueWorkType_INDIRECT);
else
throw ComponentInitException(
"Unknown/invalid work type given: " + StringTk::intToStr(workType) );
log.log(Log_DEBUG, "Component stopped.");
}
catch(std::exception& e)
{
PThread::getCurrentThreadApp()->handleComponentException(e);
}
}
void Worker::workLoop(QueueWorkType workType)
{
LOG(WORKQUEUES, DEBUG, "Ready", ("TID", System::getTID()), workType);
workQueue->incNumWorkers(); // add this worker to queue stats
while(!getSelfTerminate() || !maySelfTerminateNow() )
{
Work* work = waitForWorkByType(stats, personalWorkQueue, workType);
#ifdef BEEGFS_DEBUG_PROFILING
TimeFine workStartTime;
#endif
HighResolutionStatsTk::resetStats(&stats); // prepare stats
// process the work packet
work->process(bufIn, bufInLen, bufOut, bufOutLen);
// update stats
stats.incVals.workRequests = 1;
HighResolutionStatsTk::addHighResIncStats(*work->getHighResolutionStats(), stats);
#ifdef BEEGFS_DEBUG_PROFILING
TimeFine workEndTime;
const auto workElapsedMS = workEndTime.elapsedSinceMS(&workStartTime);
const auto workLatencyMS = workEndTime.elapsedSinceMS(work->getAgeTime());
if (workElapsedMS >= 10)
{
if (workLatencyMS >= 10)
LOG(WORKQUEUES, DEBUG, "Work processed.",
("Elapsed ms", workElapsedMS), ("Total latency (ms)", workLatencyMS));
else
LOG(WORKQUEUES, DEBUG, "Work processed.", ("Elapsed ms", workElapsedMS),
("Total latency (us)", workEndTime.elapsedSinceMicro(work->getAgeTime())));
}
else
{
if (workLatencyMS >= 10)
{
LOG(WORKQUEUES, DEBUG, "Work processed.",
("Elapsed us", workEndTime.elapsedSinceMicro(&workStartTime)),
("Total latency (ms)", workEndTime.elapsedSinceMS(work->getAgeTime())));
}
else
{
LOG(WORKQUEUES, DEBUG, "Work processed.",
("Elapsed us", workEndTime.elapsedSinceMicro(&workStartTime)),
("Total latency (us)", workEndTime.elapsedSinceMicro(work->getAgeTime())));
}
}
#endif
// cleanup
delete(work);
}
}
Work* Worker::waitForWorkByType(HighResolutionStats& newStats, PersonalWorkQueue* personalWorkQueue,
QueueWorkType workType)
{
/* note: we hope the if-conditions below are optimized away when this is called from
Worker::workLoop(), that's why we have the explicit work type arg in Worker::run() */
if(workType == QueueWorkType_DIRECT)
return workQueue->waitForDirectWork(newStats, personalWorkQueue);
else
if(workType == QueueWorkType_INDIRECT)
return workQueue->waitForAnyWork(newStats, personalWorkQueue);
else // should never happen
throw WorkerException("Unknown/invalid work type given: " + StringTk::intToStr(workType));
}
/**
* Note: For delayed buffer allocation during run(), because of NUMA-archs.
*/
void Worker::initBuffers()
{
if(this->bufInLen)
{
void* bufInVoid = NULL;
int inAllocRes = posix_memalign(&bufInVoid, sysconf(_SC_PAGESIZE), bufInLen);
IGNORE_UNUSED_VARIABLE(inAllocRes);
this->bufIn = (char*)bufInVoid;
}
if(this->bufOutLen)
{
void* bufOutVoid = NULL;
int outAllocRes = posix_memalign(&bufOutVoid, sysconf(_SC_PAGESIZE), bufOutLen);
IGNORE_UNUSED_VARIABLE(outAllocRes);
this->bufOut = (char*)bufOutVoid;
}
}

View File

@@ -0,0 +1,106 @@
#pragma once
#include <common/app/log/LogContext.h>
#include <common/app/AbstractApp.h>
#include <common/components/worker/queue/MultiWorkQueue.h>
#include <common/components/worker/queue/PersonalWorkQueue.h>
#include <common/components/ComponentInitException.h>
#include <common/threading/PThread.h>
#define WORKER_BUFIN_SIZE (1024*1024*4)
#define WORKER_BUFOUT_SIZE WORKER_BUFIN_SIZE
DECLARE_NAMEDEXCEPTION(WorkerException, "WorkerException")
class Worker : public PThread
{
public:
Worker(const std::string& workerID, MultiWorkQueue* workQueue, QueueWorkType workType);
virtual ~Worker()
{
SAFE_FREE(bufIn);
SAFE_FREE(bufOut);
SAFE_DELETE(personalWorkQueue);
}
private:
LogContext log;
bool terminateWithFullQueue; // allow self-termination when queue not empty (see setter nodes)
size_t bufInLen;
char* bufIn;
size_t bufOutLen;
char* bufOut;
MultiWorkQueue* workQueue;
QueueWorkType workType;
PersonalWorkQueue* personalWorkQueue;
HighResolutionStats stats;
virtual void run();
void workLoop(QueueWorkType workType);
Work* waitForWorkByType(HighResolutionStats& newStats, PersonalWorkQueue* personalWorkQueue,
QueueWorkType workType);
void initBuffers();
// inliners
bool maySelfTerminateNow()
{
if(terminateWithFullQueue ||
(!workQueue->getNumPendingWorks() &&
workQueue->getIsPersonalQueueEmpty(personalWorkQueue) ) )
return true;
return false;
}
public:
// setters & getters
/**
* Note: Do not use this after the run method of this component has been called!
*/
void setBufLens(size_t bufInLen, size_t bufOutLen)
{
this->bufInLen = bufInLen;
this->bufOutLen = bufOutLen;
}
MultiWorkQueue* getWorkQueue() const
{
return this->workQueue;
}
/**
* Note: Don't add anything to this queue directly, do it only via
* MultiWorkQueue->addPersonalWork().
*/
PersonalWorkQueue* getPersonalWorkQueue()
{
return this->personalWorkQueue;
}
/**
* WARNING: This will only work if there is only a single worker attached to a queue.
* Otherwise the queue would need a getWorkAndDontWait() method that is used during the
* termination phase of the worker, because the queue might become empty before the worker
* calls waitForWork() after the maySelfTerminateNow check.
*/
void disableTerminationWithFullQueue()
{
this->terminateWithFullQueue = false;
}
};

View File

@@ -0,0 +1,110 @@
#include <common/app/log/LogContext.h>
#include <common/app/AbstractApp.h>
#include <common/threading/PThread.h>
#include <common/net/message/NetMessage.h>
#include <common/toolkit/MessagingTk.h>
#include <common/nodes/NodeStoreServers.h>
#include <common/storage/StorageErrors.h>
#include <common/net/message/session/rw/WriteLocalFileMsg.h>
#include <common/net/message/session/rw/WriteLocalFileRespMsg.h>
#include "WriteLocalFileWork.h"
void WriteLocalFileWork::process(
char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen)
{
TargetMapper* targetMapper = writeInfo->targetMapper;
NodeStoreServers* nodes = writeInfo->storageNodes;
FhgfsOpsErr resolveErr;
auto node = nodes->referenceNodeByTargetID(targetID, targetMapper, &resolveErr);
if(unlikely(!node) )
{ // unable to resolve targetID
*nodeResult = -resolveErr;
}
else
{ // got node reference => begin communication
*nodeResult = communicate(*node, bufOut, bufOutLen);
}
writeInfo->counter->incCount();
}
int64_t WriteLocalFileWork::communicate(Node& node, char* bufOut, unsigned bufOutLen)
{
const char* logContext = "WriteFile Work (communication)";
AbstractApp* app = PThread::getCurrentThreadApp();
NodeConnPool* connPool = node.getConnPool();
NumNodeID localNodeNumID = writeInfo->localNodeNumID;
int64_t retVal = -FhgfsOpsErr_COMMUNICATION;
Socket* sock = NULL;
try
{
// connect
sock = connPool->acquireStreamSocket();
// prepare and send message
WriteLocalFileMsg writeMsg(localNodeNumID, fileHandleID, targetID, pathInfo,
accessFlags, offset, size);
if (this->firstWriteDoneForTarget)
writeMsg.addMsgHeaderFeatureFlag(WRITELOCALFILEMSG_FLAG_SESSION_CHECK);
unsigned msgLength = writeMsg.serializeMessage(bufOut, bufOutLen).second;
sock->send(bufOut, msgLength, 0);
writeMsg.sendData(buf, sock);
// receive response
auto resp = MessagingTk::recvMsgBuf(*sock);
if (resp.empty())
{ // error
LogContext log(logContext);
log.log(Log_WARNING, "Did not receive a response from: " + sock->getPeername() );
}
else
{ // got response => deserialize it
auto respMsg = app->getNetMessageFactory()->createFromBuf(std::move(resp));
if(respMsg->getMsgType() != NETMSGTYPE_WriteLocalFileResp)
{ // response invalid (wrong msgType)
LogContext log(logContext);
log.logErr(std::string("Received invalid response type: ") +
StringTk::intToStr(respMsg->getMsgType() ) + std::string(". ") +
std::string("Expected: ") + StringTk::intToStr(NETMSGTYPE_WriteLocalFileResp) +
std::string(". ") +
std::string("Disconnecting: ") + sock->getPeername() );
}
else
{ // correct response => return it
connPool->releaseStreamSocket(sock);
WriteLocalFileRespMsg* writeRespMsg = (WriteLocalFileRespMsg*)respMsg.get();
return writeRespMsg->getValue();
}
}
}
catch(SocketConnectException& e)
{
LogContext(logContext).log(Log_WARNING, std::string("Unable to connect to storage node: ") +
node.getAlias() );
retVal = -FhgfsOpsErr_COMMUNICATION;
}
catch(SocketException& e)
{
LogContext(logContext).logErr(std::string("SocketException: ") + e.what() );
LogContext(logContext).log(Log_WARNING, "Values for sessionID/handleID/offset/size: " +
localNodeNumID.str() + "/" + fileHandleID +
"/" + StringTk::int64ToStr(offset) + "/" + StringTk::int64ToStr(size) );
retVal = -FhgfsOpsErr_COMMUNICATION;
}
// clean up
if(sock)
connPool->invalidateStreamSocket(sock);
return retVal;
}

View File

@@ -0,0 +1,76 @@
#pragma once
#include <common/Common.h>
#include <common/net/sock/Socket.h>
#include <common/components/worker/Work.h>
#include <common/toolkit/SynchronizedCounter.h>
#include <common/nodes/NumNodeID.h>
#include <common/nodes/TargetMapper.h>
#include <common/nodes/NodeStoreServers.h>
struct WriteLocalFileWorkInfo
{
WriteLocalFileWorkInfo(NumNodeID localNodeNumID, TargetMapper* targetMapper,
NodeStoreServers* storageNodes, SynchronizedCounter* counter)
: localNodeNumID(localNodeNumID), targetMapper(targetMapper), storageNodes(storageNodes),
counter(counter)
{
// all init done in initializer list
}
NumNodeID localNodeNumID;
TargetMapper* targetMapper;
NodeStoreServers* storageNodes;
SynchronizedCounter* counter;
};
class WriteLocalFileWork : public Work
{
public:
/**
* Note: pathInfo is not owned by this object.
*/
WriteLocalFileWork(const char* fileHandleID, const char* buf, unsigned accessFlags,
off_t offset, size_t size, uint16_t targetID, PathInfo* pathInfo, int64_t* nodeResult,
WriteLocalFileWorkInfo* writeInfo, bool firstWriteDoneForTarget)
{
this->fileHandleID = fileHandleID;
this->buf = buf;
this->accessFlags = accessFlags;
this->offset = offset;
this->size = size;
this->targetID = targetID;
this->pathInfo = pathInfo;
this->nodeResult = nodeResult;
this->writeInfo = writeInfo;
this->firstWriteDoneForTarget = firstWriteDoneForTarget;
}
virtual ~WriteLocalFileWork()
{
}
virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen);
private:
const char* fileHandleID;
const char* buf;
unsigned accessFlags;
off_t offset;
size_t size;
uint16_t targetID;
PathInfo* pathInfo;
int64_t* nodeResult;
WriteLocalFileWorkInfo* writeInfo;
bool firstWriteDoneForTarget; /* true if the first chunk was written to the storage target,
it's needed for the session check*/
int64_t communicate(Node& node, char* bufOut, unsigned bufOutLen);
};

View File

@@ -0,0 +1,29 @@
#pragma once
#include <common/components/worker/Work.h>
#include <common/Common.h>
typedef std::list<Work*> WorkList;
typedef WorkList::iterator WorkListIter;
/**
* Interface for ordered work containers. This is to unify access to trivial std::list based
* internal work list and the special user-based fair work list.
*/
class AbstractWorkContainer
{
public:
virtual ~AbstractWorkContainer() {};
virtual Work* getAndPopNextWork() = 0;
virtual void addWork(Work* work, unsigned userID) = 0;
virtual size_t getSize() = 0;
virtual bool getIsEmpty() = 0;
virtual void getStatsAsStr(std::string& outStats) = 0;
};

View File

@@ -0,0 +1,61 @@
#pragma once
#include <common/Common.h>
#include "AbstractWorkContainer.h"
/**
* Simple implementation of AbstractWorkContainer interface based on a single std::list.
*
* This container ignores the given userIDs.
*/
class ListWorkContainer : public AbstractWorkContainer
{
public:
virtual ~ListWorkContainer()
{
for(WorkListIter iter = workList.begin(); iter != workList.end(); iter++)
delete(*iter);
}
private:
WorkList workList;
public:
// inliners
Work* getAndPopNextWork()
{
Work* work = *workList.begin();
workList.pop_front();
return work;
}
void addWork(Work* work, unsigned userID)
{
workList.push_back(work);
}
size_t getSize()
{
return workList.size();
}
bool getIsEmpty()
{
return workList.empty();
}
void getStatsAsStr(std::string& outStats)
{
std::ostringstream statsStream;
statsStream << "* Queue type: ListWorkContainer" << std::endl;
statsStream << "* Queue len: " << workList.size() << std::endl;
outStats = statsStream.str();
}
};

View File

@@ -0,0 +1,227 @@
#include "MultiWorkQueue.h"
#include "PersonalWorkQueue.h"
MultiWorkQueue::MultiWorkQueue()
{
numPendingWorks = 0;
lastWorkListVecIdx = 0;
directWorkList = new ListWorkContainer();
indirectWorkList = new ListWorkContainer();
// we use QueueWorkType_... as vec index, so order must be same as in QueueWorkType
workListVec.push_back(directWorkList);
workListVec.push_back(indirectWorkList);
HighResolutionStatsTk::resetStats(&stats);
}
MultiWorkQueue::~MultiWorkQueue()
{
delete(directWorkList);
delete(indirectWorkList);
}
/**
* Code base of waitForDirectWork.
*/
Work* MultiWorkQueue::waitForDirectWork(HighResolutionStats& newStats,
PersonalWorkQueue* personalWorkQueue)
{
std::lock_guard<Mutex> mutexLock(mutex);
HighResolutionStatsTk::addHighResIncStats(newStats, stats);
stats.rawVals.busyWorkers--;
while(directWorkList->getIsEmpty() && likely(personalWorkQueue->getIsWorkListEmpty() ) )
newDirectWorkCond.wait(&mutex);
stats.rawVals.busyWorkers++;
// personal is always first
if(unlikely(!personalWorkQueue->getIsWorkListEmpty() ) )
{ // we got something in our personal queue
Work* work = personalWorkQueue->getAndPopFirstWork();
#ifdef BEEGFS_DEBUG_PROFILING
const auto workAgeMS = work->getAgeTime()->elapsedMS();
if (workAgeMS > 10)
LOG(WORKQUEUES, DEBUG, "Fetching personal work item.", work,
("age (ms)", workAgeMS));
else
LOG(WORKQUEUES, DEBUG, "Fetching personal work item.", work,
("age (us)", work->getAgeTime()->elapsedMicro()));
#endif
return work;
}
else
{
Work* work = directWorkList->getAndPopNextWork();
numPendingWorks--;
#ifdef BEEGFS_DEBUG_PROFILING
const auto workAgeMS = work->getAgeTime()->elapsedMS();
if (workAgeMS > 10)
LOG(WORKQUEUES, DEBUG, "Fetching direct work item.",
work, ("age (ms)", workAgeMS));
else
LOG(WORKQUEUES, DEBUG, "Fetching direct work item.",
work, ("age (us)", work->getAgeTime()->elapsedMicro()));
#endif
return work;
}
}
/**
* Wait for work on any of the avialable queues. This is for indirect (non-specialized) workers.
*
* @param newStats the updated stats from processing of the last work package.
* @param personalWorkQueue the personal queue of the worker thread which called this method.
*/
Work* MultiWorkQueue::waitForAnyWork(HighResolutionStats& newStats,
PersonalWorkQueue* personalWorkQueue)
{
std::lock_guard<Mutex> mutexLock(mutex);
HighResolutionStatsTk::addHighResIncStats(newStats, stats);
stats.rawVals.busyWorkers--;
while(!numPendingWorks && likely(personalWorkQueue->getIsWorkListEmpty() ) )
{ // no work available right now
newWorkCond.wait(&mutex);
}
stats.rawVals.busyWorkers++;
// sanity check: ensure numPendingWorks and actual number of works are equal
#ifdef BEEGFS_DEBUG
size_t numQueuedWorks = 0;
for(unsigned i=0; i < QueueWorkType_FINAL_DONTUSE; i++)
numQueuedWorks += workListVec[i]->getSize();
if(unlikely(numQueuedWorks != numPendingWorks) )
throw MultiWorkQueueException("numQueuedWorks != numPendingWorks: " +
StringTk::uint64ToStr(numQueuedWorks) + "!=" + StringTk::uint64ToStr(numPendingWorks) );
#endif // BEEGFS_DEBUG
// personal is always first
if(unlikely(!personalWorkQueue->getIsWorkListEmpty() ) )
{ // we got something in our personal queue
Work* work = personalWorkQueue->getAndPopFirstWork();
#ifdef BEEGFS_DEBUG_PROFILING
const auto workAgeMS = work->getAgeTime()->elapsedMS();
if (workAgeMS > 10)
LOG(WORKQUEUES, DEBUG, "Fetching personal work item.",
work, ("age (ms)", workAgeMS));
else
LOG(WORKQUEUES, DEBUG, "Fetching personal work item.",
work, ("age (us)", work->getAgeTime()->elapsedMicro()));
#endif
return work;
}
else
{
// walk over all available queues
// (note: lastWorkListVecIdx ensures that all queue are checked in a fair way)
for(unsigned i=0; i < QueueWorkType_FINAL_DONTUSE; i++)
{
// switch to next work queue
lastWorkListVecIdx++;
lastWorkListVecIdx = lastWorkListVecIdx % QueueWorkType_FINAL_DONTUSE;
AbstractWorkContainer* currentWorkList = workListVec[lastWorkListVecIdx];
if(!currentWorkList->getIsEmpty() )
{ // this queue contains work for us
Work* work = currentWorkList->getAndPopNextWork();
numPendingWorks--;
#ifdef BEEGFS_DEBUG_PROFILING
const std::string direct = (i == QueueWorkType_DIRECT) ? "direct" : "indirect";
const auto workAgeMS = work->getAgeTime()->elapsedMS();
if (workAgeMS > 10)
LOG(WORKQUEUES, DEBUG, "Fetching direct work item.",
work, ("age (ms)", workAgeMS));
else
LOG(WORKQUEUES, DEBUG, "Fetching direct work item.",
work, ("age (us)", work->getAgeTime()->elapsedMicro()));
#endif
return work;
}
}
// we should never get here: all queues are empty
throw MultiWorkQueueException("Unexpected in " + std::string(__func__) + ": "
"All queues are empty. "
"numPendingWorks: " + StringTk::uint64ToStr(numPendingWorks) );
}
}
/**
* Adds a new worker thread to internal stats counters to provide correct number of idle/busy
* workers.
*
* This must be called exactly once by each worker before the worker calls waitFor...Work().
*/
void MultiWorkQueue::incNumWorkers()
{
std::lock_guard<Mutex> mutesLock(mutex);
/* note: we increase number of busy workers here, because this value will be decreased
by 1 when the worker calls waitFor...Work(). */
stats.rawVals.busyWorkers++;
}
/**
* Deletes the old list and replaces it with the new given one.
*
* Note: Unlocked, because this is intended to be called during queue preparation.
*
* @param newWorkList will be owned by this class, so do no longer access it directly and don't
* delete it.
*/
void MultiWorkQueue::setIndirectWorkList(AbstractWorkContainer* newWorkList)
{
#ifdef BEEGFS_DEBUG
// sanity check
if(!indirectWorkList->getIsEmpty() )
throw MultiWorkQueueException("Unexpected in " + std::string(__func__) + ": "
"Queue to be replaced is not empty.");
#endif // BEEGFS_DEBUG
delete(indirectWorkList);
indirectWorkList = newWorkList;
workListVec[QueueWorkType_INDIRECT] = indirectWorkList;
}
/**
* Note: Holds lock while generating stats strings => slow => use carefully
*/
void MultiWorkQueue::getStatsAsStr(std::string& outIndirectQueueStats,
std::string& outDirectQueueStats, std::string& outBusyStats)
{
std::lock_guard<Mutex> mutexLock(mutex);
// get queue stats
indirectWorkList->getStatsAsStr(outIndirectQueueStats);
directWorkList->getStatsAsStr(outDirectQueueStats);
// number of busy workers
std::ostringstream busyStream;
busyStream << "* Busy workers: " << StringTk::uintToStr(stats.rawVals.busyWorkers) << std::endl;
busyStream << "* Work Requests: " << StringTk::uintToStr(stats.incVals.workRequests) << " "
"(reset every second)" << std::endl;
busyStream << "* Bytes read: " << StringTk::uintToStr(stats.incVals.diskReadBytes) << " "
"(reset every second)" << std::endl;
busyStream << "* Bytes written: " << StringTk::uintToStr(stats.incVals.diskWriteBytes) << " "
"(reset every second)" << std::endl;
outBusyStats = busyStream.str();
}

View File

@@ -0,0 +1,174 @@
#pragma once
#include <common/app/log/LogContext.h>
#include <common/components/worker/queue/StreamListenerWorkQueue.h>
#include <common/components/worker/Work.h>
#include <common/threading/Mutex.h>
#include <common/threading/Condition.h>
#include <common/toolkit/NamedException.h>
#include <common/toolkit/HighResolutionStats.h>
#include <common/toolkit/Time.h>
#include <common/Common.h>
#include "ListWorkContainer.h"
#include "PersonalWorkQueue.h"
#include <mutex>
#define MULTIWORKQUEUE_DEFAULT_USERID (~0) // (usually similar to NETMESSAGE_DEFAULT_USERID)
DECLARE_NAMEDEXCEPTION(MultiWorkQueueException, "MultiWorkQueueException")
class MultiWorkQueue; // forward declaration
typedef std::map<uint16_t, MultiWorkQueue*> MultiWorkQueueMap; // maps targetIDs to WorkQueues
typedef MultiWorkQueueMap::iterator MultiWorkQueueMapIter;
typedef MultiWorkQueueMap::const_iterator MultiWorkQueueMapCIter;
typedef MultiWorkQueueMap::value_type MultiWorkQueueMapVal;
/**
* Note: We also use these numbers as indices in the MultiWorkQueue::workListVec, so carefully
* check all related cases when you add/change something here.
*/
enum QueueWorkType
{
QueueWorkType_DIRECT=0,
QueueWorkType_INDIRECT,
QueueWorkType_FINAL_DONTUSE // the final element as terminating vector index
};
class MultiWorkQueue : public StreamListenerWorkQueue
{
private:
// type definitions
typedef std::vector<AbstractWorkContainer*> WorkListVec;
typedef WorkListVec::iterator WorkListVecIter;
typedef WorkListVec::const_iterator WorkListVecCIter;
public:
MultiWorkQueue();
~MultiWorkQueue();
Work* waitForDirectWork(HighResolutionStats& newStats, PersonalWorkQueue* personalWorkQueue);
Work* waitForAnyWork(HighResolutionStats& newStats, PersonalWorkQueue* personalWorkQueue);
void incNumWorkers();
void setIndirectWorkList(AbstractWorkContainer* newWorkList);
void getStatsAsStr(std::string& outIndirectQueueStats, std::string& outDirectQueueStats,
std::string& outBusyStats);
private:
AbstractWorkContainer* directWorkList;
AbstractWorkContainer* indirectWorkList;
Mutex mutex;
Condition newDirectWorkCond; // direct workers wait only on this condition
Condition newWorkCond; // for any type of work (indirect workers wait on this condition)
size_t numPendingWorks; // length of direct+indirect list (not incl personal list)
unsigned lastWorkListVecIdx; // toggles indirect workers types of work (% queue types)
WorkListVec workListVec; // used to toggle next work type with nextWorkType as index
HighResolutionStats stats;
public:
void addDirectWork(Work* work, unsigned userID = MULTIWORKQUEUE_DEFAULT_USERID)
{
#ifdef BEEGFS_DEBUG_PROFILING
LOG(WORKQUEUES, DEBUG, "Adding direct work item.", work);
#endif
std::lock_guard<Mutex> mutexLock(mutex);
directWorkList->addWork(work, userID);
numPendingWorks++;
newWorkCond.signal();
newDirectWorkCond.signal();
}
void addIndirectWork(Work* work, unsigned userID = MULTIWORKQUEUE_DEFAULT_USERID)
{
#ifdef BEEGFS_DEBUG_PROFILING
LOG(WORKQUEUES, DEBUG, "Adding indirect work item.", work);
#endif
std::lock_guard<Mutex> mutexLock(mutex);
indirectWorkList->addWork(work, userID);
numPendingWorks++;
newWorkCond.signal();
}
void addPersonalWork(Work* work, PersonalWorkQueue* personalQ)
{
/* note: this is in the here (instead of the PersonalWorkQueue) because the MultiWorkQueue
mutex also syncs the personal queue. */
std::lock_guard<Mutex> mutexLock(mutex);
personalQ->addWork(work);
// note: we do not increase numPendingWorks here (it is only for the other queues)
// we assume this method is rarely used, so we just wake up all wokers (inefficient)
newDirectWorkCond.broadcast();
newWorkCond.broadcast();
}
size_t getDirectWorkListSize()
{
std::lock_guard<Mutex> mutexLock(mutex);
return directWorkList->getSize();
}
size_t getIndirectWorkListSize()
{
std::lock_guard<Mutex> mutexLock(mutex);
return indirectWorkList->getSize();
}
bool getIsPersonalQueueEmpty(PersonalWorkQueue* personalQ)
{
std::lock_guard<Mutex> mutexLock(mutex);
return personalQ->getIsWorkListEmpty();
}
size_t getNumPendingWorks()
{
std::lock_guard<Mutex> mutexLock(mutex);
return numPendingWorks;
}
/**
* Returns current stats and _resets_ them.
*/
void getAndResetStats(HighResolutionStats* outStats)
{
std::lock_guard<Mutex> mutexLock(mutex);
*outStats = stats;
outStats->rawVals.queuedRequests = numPendingWorks;
/* note: we only reset incremental stats vals, because otherwise we would lose info
like number of busyWorkers */
HighResolutionStatsTk::resetIncStats(&stats);
}
};

View File

@@ -0,0 +1,77 @@
#pragma once
#include <common/components/worker/Work.h>
#include <common/toolkit/NamedException.h>
#include <common/Common.h>
DECLARE_NAMEDEXCEPTION(PersonalWorkQueueException, "PersonalWorkQueueException")
/*
* Personal queues are intended to assign work directly to a specific worker thread.
*
* This is useful when we need to make sure that each worker gets a certain work request at least
* once, e.g. to synchronize workers for fsck modification logging.
*
* This class has no own mutex for thread-safety, it is sync'ed via the MultiWorkQueue mutex.
* So adding work to it is done via MultiWorkQueue methods.
*
* Note: Workers always prefer requests in the personal queue over requests in the other queues,
* so keep possible starvation of requests in other queues in mind when you use personal queues.
*/
class PersonalWorkQueue
{
friend class MultiWorkQueue; /* to make sure that our methods are not called without the
MultiWorkQueue mutex being held. */
public:
PersonalWorkQueue() {}
~PersonalWorkQueue()
{
// delete remaining work packets
for(WorkListIter iter = workList.begin(); iter != workList.end(); iter++)
delete(*iter);
}
private:
WorkList workList;
private:
// inliners
void addWork(Work* work)
{
workList.push_back(work);
}
/*
* get the next work package (and remove it from the queue).
* caller must ensure that the queue is not empty before calling this.
*/
Work* getAndPopFirstWork()
{
if(unlikely(workList.empty() ) ) // should never happen
throw PersonalWorkQueueException("Work requested from empty queue");
Work* work = *workList.begin();
workList.pop_front();
return work;
}
bool getIsWorkListEmpty()
{
return workList.empty();
}
size_t getWorkListSize()
{
return workList.size();
}
};

View File

@@ -0,0 +1,21 @@
#pragma once
#include <common/components/worker/Work.h>
#define STREAMLISTENERWORKQUEUE_DEFAULT_USERID (~0) // usually similar to NETMESSAGE_DEFAULT_USERID
class StreamListenerWorkQueue
{
public:
virtual ~StreamListenerWorkQueue() {};
virtual void addDirectWork(Work* work,
unsigned userID = STREAMLISTENERWORKQUEUE_DEFAULT_USERID) = 0;
virtual void addIndirectWork(Work* work,
unsigned userID = STREAMLISTENERWORKQUEUE_DEFAULT_USERID) = 0;
};

View File

@@ -0,0 +1,176 @@
#pragma once
#include <common/toolkit/NamedException.h>
#include <common/Common.h>
#include "AbstractWorkContainer.h"
DECLARE_NAMEDEXCEPTION(UserWorkContainerException, "UserWorkContainerException")
/**
* Implementation of AbstractWorkContainer interface based on per-user queues for improved fairness.
*
* Work packets will be taken from the different user queues in a round-robin fashion, so that in
* theory a user with a short queue has the same chance of being assigned to the next free worker as
* another user with a long queue.
* Of course, the global fairness is influenced by other scheduling as well, e.g. at the network
* and disk level.
*
* Note on nextWorkIter: A possible unwanted unfairness happens if we have e.g. two active users,
* where userA writes with n threads, userB writes with only a single thread; then we would see a
* 2:1 msg processing ratio with a single worker like B, A1, A2, B, A1, A2, B, ...
* This is because when A1 msg is popped, next iter points to end(), because next B request isn't in
* queue yet, so on next pop we use begin(), which is A2 even though the next B request came in
* while we processed A1.
* We accept this, because avoiding unwanted anomalies completely would get very complex, even with
* a lastWorkIter (e.g. would require starvation handling when new work is inserted between "last"
* and "next") or keeping empty queues for another round.
*/
class UserWorkContainer : public AbstractWorkContainer
{
// typedefs...
typedef std::map<unsigned, WorkList> UserWorkMap; // key: userID; value: per-user work queue
typedef UserWorkMap::iterator UserWorkMapIter;
typedef UserWorkMap::const_iterator UserWorkMapCIter;
typedef UserWorkMap::value_type UserWorkMapVal;
public:
UserWorkContainer()
{
numWorks = 0;
nextWorkIter = workMap.begin(); // initialize by pointing to end() (=> map empty)
}
virtual ~UserWorkContainer()
{
// walk over all userIDs and delete all elems of their queues...
for(UserWorkMapIter mapIter = workMap.begin(); mapIter != workMap.end(); mapIter++)
{
WorkList& workList = mapIter->second;
for(WorkListIter listIter = workList.begin(); listIter != workList.end(); listIter++)
delete(*listIter);
}
}
private:
UserWorkMap workMap; // entries added on demand and removed when queue empty
size_t numWorks; // number of works in all queues
UserWorkMapIter nextWorkIter; // points to next work (or end() if currently not set)
public:
// inliners
Work* getAndPopNextWork()
{
#ifdef BEEGFS_DEBUG
// sanity check
if(workMap.empty() )
throw UserWorkContainerException(
"Sanity check failed in " + std::string(__func__) + ": "
"caller tried to get work from empty queue");
#endif // BEEGFS_DEBUG
if(nextWorkIter == workMap.end() )
nextWorkIter = workMap.begin();
/* note on iters after map modification: The C++ standard mandates...
"The insert members shall not affect the validity of iterators and references to the
container, and the erase members shall invalidate only iterators and references to the
erased elements." */
UserWorkMapIter currentWorkIter = nextWorkIter;
WorkList& workList = currentWorkIter->second;
nextWorkIter++; // move to next elem
#ifdef BEEGFS_DEBUG
// sanity check
if(workList.empty() )
throw UserWorkContainerException(
"Sanity check failed in " + std::string(__func__) + ": "
"user queue is empty");
#endif // BEEGFS_DEBUG
Work* work = *workList.begin();
workList.pop_front();
numWorks--;
if(workList.empty() )
workMap.erase(currentWorkIter); // remove userID with empty work list
#ifdef BEEGFS_DEBUG
// sanity checks...
if(numWorks < workMap.size() )
throw UserWorkContainerException(
"Sanity check failed in " + std::string(__func__) + ": "
"numWorks < workMap.size(): " +
StringTk::uintToStr(numWorks) + "<" + StringTk::uintToStr(workMap.size() ) );
if(workMap.empty() && numWorks)
throw UserWorkContainerException(
"Sanity check failed in " + std::string(__func__) + ": "
"workMap is empty, but numWorks==" + StringTk::uintToStr(numWorks) );
#endif // BEEGFS_DEBUG
return work;
}
void addWork(Work* work, unsigned userID)
{
// (note: the [] operator implicitly created the key/value pair if it didn't exist yet)
workMap[userID].push_back(work);
numWorks++;
}
size_t getSize()
{
return numWorks;
}
bool getIsEmpty()
{
return !numWorks;
}
void getStatsAsStr(std::string& outStats)
{
std::ostringstream statsStream;
statsStream << "* Queue type: UserWorkContainer" << std::endl;
statsStream << "* Num works total: " << numWorks << std::endl;
statsStream << "* Num user queues: " << workMap.size() << std::endl;
statsStream << std::endl;
if(workMap.empty() )
{ // no individual stats to be printed
outStats = statsStream.str();
return;
}
statsStream << "Individual queue stats (user/qlen)..." << std::endl;
for(UserWorkMapCIter mapIter = workMap.begin(); mapIter != workMap.end(); mapIter++)
{
// we use int for userID because NETMSG_DEFAULT_USERID looks better signed
int userID = mapIter->first;
const WorkList& workList = mapIter->second;
statsStream << "* " << "UserID " << userID << ": " << workList.size() << std::endl;
}
outStats = statsStream.str();
}
};

View File

@@ -0,0 +1,48 @@
#pragma once
#include <common/components/worker/Work.h>
#include <common/threading/Mutex.h>
#include <common/threading/Condition.h>
#include <common/Common.h>
#include <mutex>
class WorkQueue
{
public:
WorkQueue() {}
~WorkQueue()
{
for(WorkListIter iter = workList.begin(); iter != workList.end(); iter++)
delete(*iter);
}
Work* waitForNewWork()
{
std::lock_guard<Mutex> mutexLock(mutex);
while(workList.empty() )
newWorkCond.wait(&mutex);
Work* work = *workList.begin();
workList.pop_front();
return work;
}
void addWork(Work* work)
{
std::lock_guard<Mutex> mutexLock(mutex);
newWorkCond.signal();
workList.push_back(work);
}
private:
WorkList workList;
Mutex mutex;
Condition newWorkCond;
};

View File

@@ -0,0 +1,239 @@
#pragma once
#include <common/Common.h>
#include <common/storage/Path.h>
#include <common/storage/PathInfo.h>
#include <common/toolkit/serialization/Serialization.h>
#include <iostream>
class FsckChunk;
typedef std::list<FsckChunk> FsckChunkList;
typedef FsckChunkList::iterator FsckChunkListIter;
class FsckChunk
{
friend class TestDatabase;
public:
/*
* @param id the chunk id
* @param targetID the id of the target, on which the chunk is saved
* @param savedPath the path, where the chunk was found; relative to storeStorageDirectory
* @param filesize the size of the chunk in byte
* @param usedBlocks the actual used blocks on the FS
* @param creationTime creation time in secs since the epoch
* @param lastAccessTime last access time in secs since the epoch
* @param modificationTime modification time in secs since the epoch
* @param userID
* @param groupID
* @param buddyGroupID the buddyGroupID this chunk belongs to
*/
FsckChunk(const std::string& id, uint16_t targetID, const Path& savedPath, int64_t fileSize,
uint64_t usedBlocks, int64_t creationTime, int64_t modificationTime, int64_t lastAccessTime,
unsigned userID, unsigned groupID, uint16_t buddyGroupID) :
id(id), targetID(targetID), savedPath(savedPath), fileSize(fileSize),
usedBlocks(usedBlocks), creationTime(creationTime), modificationTime(modificationTime),
lastAccessTime(lastAccessTime), userID(userID), groupID(groupID),
buddyGroupID(buddyGroupID)
{
}
/*
* @param id the chunk id
* @param targetID the id of the target, on which the chunk is saved
* @param savedPath the path, where the chunk was found; relative to storeStorageDirectory
* @param filesize the size of the chunk in byte
* @param usedBlocks the actual used blocks on the FS
* @param creationTime creation time in secs since the epoch
* @param lastAccessTime last access time in secs since the epoch
* @param modificationTime modification time in secs since the epoch
* @param userID
* @param groupID
*/
FsckChunk(const std::string& id, uint16_t targetID, const Path& savedPath,
int64_t fileSize, int64_t usedBlocks, int64_t creationTime, int64_t modificationTime,
int64_t lastAccessTime, unsigned userID, unsigned groupID) :
id(id), targetID(targetID), savedPath(savedPath), fileSize(fileSize),
usedBlocks(usedBlocks), creationTime(creationTime), modificationTime(modificationTime),
lastAccessTime(lastAccessTime), userID(userID), groupID(groupID), buddyGroupID(0)
{
}
// only for deserialization!
FsckChunk()
{
}
private:
std::string id;
uint16_t targetID;
Path savedPath; // the path, where the chunk was found; relative to storeStorageDirectory
int64_t fileSize; // in byte
uint64_t usedBlocks;
int64_t creationTime; // secs since the epoch
int64_t modificationTime; // secs since the epoch
int64_t lastAccessTime; // secs since the epoch
uint32_t userID;
uint32_t groupID;
uint16_t buddyGroupID; // if this is a buddy mirrored chunk, this field saves the
// buddyGroupID
public:
const std::string& getID() const
{
return this->id;
}
uint16_t getTargetID() const
{
return this->targetID;
}
Path* getSavedPath()
{
return &(this->savedPath);
}
void setSavedPath(const Path& path)
{
this->savedPath = path;
}
int64_t getFileSize() const
{
return this->fileSize;
}
int64_t getUsedBlocks() const
{
return this->usedBlocks;
}
int64_t getCreationTime() const
{
return this->creationTime;
}
int64_t getModificationTime() const
{
return this->modificationTime;
}
int64_t getLastAccessTime() const
{
return this->lastAccessTime;
}
unsigned getUserID() const
{
return this->userID;
}
void setUserID(const unsigned userID)
{
this->userID = userID;
}
unsigned getGroupID() const
{
return this->groupID;
}
void setGroupID(const unsigned groupID)
{
this->groupID = groupID;
}
uint16_t getBuddyGroupID() const
{
return this->buddyGroupID;
}
bool operator<(const FsckChunk& other) const
{
if ( id < other.id )
return true;
else
return false;
}
bool operator==(const FsckChunk& other) const
{
if ( id != other.id )
return false;
else
if ( targetID != other.targetID )
return false;
else
if ( savedPath != other.savedPath )
return false;
else
if ( fileSize != other.fileSize )
return false;
else
if ( usedBlocks != other.usedBlocks )
return false;
else
if ( creationTime != other.creationTime )
return false;
else
if ( lastAccessTime != other.lastAccessTime )
return false;
else
if ( modificationTime != other.modificationTime )
return false;
else
if ( userID != other.userID )
return false;
else
if ( groupID != other.groupID )
return false;
else
if ( buddyGroupID != other.buddyGroupID )
return false;
else
return true;
}
bool operator!= (const FsckChunk& other) const
{
return !(operator==( other ) );
}
void print()
{
std::cout << "id: " << id << '\n'
<< "targetID: " << targetID << '\n'
<< "savedPath: " << savedPath.str() << '\n'
<< "fileSize: " << fileSize << '\n'
<< "usedBlocks: " << usedBlocks << '\n'
<< "creationTime: " << creationTime << '\n'
<< "modificationTime: " << modificationTime << '\n'
<< "lastAccessTime: " << lastAccessTime << '\n'
<< "userID: " << userID << '\n'
<< "buddyGroupID: " << buddyGroupID << std::endl;
}
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% serdes::stringAlign4(obj->id)
% obj->targetID
% obj->savedPath
% obj->fileSize
% obj->usedBlocks
% obj->creationTime
% obj->modificationTime
% obj->lastAccessTime
% obj->userID
% obj->groupID
% obj->buddyGroupID;
}
};
template<>
struct ListSerializationHasLength<FsckChunk> : boost::false_type {};

View File

@@ -0,0 +1,72 @@
#pragma once
#include <common/Common.h>
#include <common/nodes/NumNodeID.h>
#include <common/toolkit/serialization/Serialization.h>
class FsckContDir;
typedef std::list<FsckContDir> FsckContDirList;
typedef FsckContDirList::iterator FsckContDirListIter;
class FsckContDir
{
private:
std::string id;
NumNodeID saveNodeID;
bool isBuddyMirrored;
public:
FsckContDir(const std::string& id, NumNodeID saveNodeID, bool isBuddyMirrored) :
id(id), saveNodeID(saveNodeID), isBuddyMirrored(isBuddyMirrored)
{
}
//only for deserialization
FsckContDir() {}
const std::string& getID() const
{
return this->id;
}
NumNodeID getSaveNodeID() const
{
return saveNodeID;
}
bool getIsBuddyMirrored() const { return isBuddyMirrored; }
bool operator< (const FsckContDir& other) const
{
if (id < other.id)
return true;
else
return false;
}
bool operator== (const FsckContDir& other) const
{
return id == other.id &&
saveNodeID == other.saveNodeID &&
isBuddyMirrored == other.isBuddyMirrored;
}
bool operator!= (const FsckContDir& other) const
{
return !(operator==( other ) );
}
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% obj->id
% obj->saveNodeID
% obj->isBuddyMirrored;
}
};
template<>
struct ListSerializationHasLength<FsckContDir> : boost::false_type {};

View File

@@ -0,0 +1,263 @@
#pragma once
#include <common/Common.h>
#include <common/nodes/NumNodeID.h>
#include <common/toolkit/serialization/Serialization.h>
#define FsckDirEntryType_ISDIR(dirEntryType) (dirEntryType == FsckDirEntryType_DIRECTORY)
#define FsckDirEntryType_ISREGULARFILE(dirEntryType) (dirEntryType == FsckDirEntryType_REGULARFILE)
#define FsckDirEntryType_ISSYMLINK(dirEntryType) (dirEntryType == FsckDirEntryType_SYMLINK)
#define FsckDirEntryType_ISSPECIAL(dirEntryType) (dirEntryType == FsckDirEntryType_SPECIAL)
#define FsckDirEntryType_ISVALID(dirEntryType) (dirEntryType != FsckDirEntryType_INVALID)
enum FsckDirEntryType
{
FsckDirEntryType_INVALID = 0,
FsckDirEntryType_DIRECTORY = 1,
FsckDirEntryType_REGULARFILE = 2,
FsckDirEntryType_SYMLINK = 3,
FsckDirEntryType_SPECIAL = 4 // BLOCKDEV,CHARDEV,FIFO,etc. are not important for fsck
};
class FsckDirEntry;
typedef std::list<FsckDirEntry> FsckDirEntryList;
typedef FsckDirEntryList::iterator FsckDirEntryListIter;
class FsckDirEntry
{
friend class TestDatabase;
private:
std::string id; // a filesystem-wide identifier for this entry
std::string name; // the user-friendly name
std::string parentDirID;
NumNodeID entryOwnerNodeID;
NumNodeID inodeOwnerNodeID; // 0 for unknown owner
/* Note : The DirEntryTypes are stored as integers in the DB;
always keep them in sync with enum FsckDirEntryType! */
FsckDirEntryType entryType;
bool hasInlinedInode;
bool isBuddyMirrored;
NumNodeID saveNodeID;
int32_t saveDevice; // the device on which this dentry file is saved (according to stat)
uint64_t saveInode; // the inode of this dentry file (according to stat)
// used in fsck database to identify dentries with a compact value instead of the full
// name
uint64_t internalID;
public:
FsckDirEntry(const std::string& id, const std::string& name, const std::string& parentDirID,
NumNodeID entryOwnerNodeID, NumNodeID inodeOwnerNodeID, FsckDirEntryType entryType,
bool hasInlinedInode, NumNodeID saveNodeID, int saveDevice, uint64_t saveInode,
bool isBuddyMirrored, uint64_t internalID = 0)
: id(id), name(name), parentDirID(parentDirID), entryOwnerNodeID(entryOwnerNodeID),
inodeOwnerNodeID(inodeOwnerNodeID), entryType(entryType),
hasInlinedInode(hasInlinedInode), isBuddyMirrored(isBuddyMirrored),
saveNodeID(saveNodeID), saveDevice(saveDevice), saveInode(saveInode),
internalID(internalID)
{}
//only for deserialization
FsckDirEntry() {}
const std::string& getID() const
{
return this->id;
}
void setID(const std::string& id)
{
this->id = id;
}
const std::string& getName() const
{
return this->name;
}
void setName(const std::string& name)
{
this->name = name;
}
const std::string& getParentDirID() const
{
return this->parentDirID;
}
void setParentDirID(const std::string& parentDirID)
{
this->parentDirID = parentDirID;
}
NumNodeID getEntryOwnerNodeID() const
{
return this->entryOwnerNodeID;
}
void setEntryOwnerNodeID(const NumNodeID entryOwnerNodeID)
{
this->entryOwnerNodeID = entryOwnerNodeID;
}
NumNodeID getInodeOwnerNodeID() const
{
return this->inodeOwnerNodeID;
}
void setInodeOwnerNodeID(const NumNodeID inodeOwnerNodeID)
{
this->inodeOwnerNodeID = inodeOwnerNodeID;
}
FsckDirEntryType getEntryType() const
{
return this->entryType;
}
void setEntryType(const FsckDirEntryType entryType)
{
this->entryType = entryType;
}
bool getHasInlinedInode() const
{
return hasInlinedInode;
}
void setHasInlinedInode(const bool hasInlinedInode)
{
this->hasInlinedInode = hasInlinedInode;
}
NumNodeID getSaveNodeID() const
{
return saveNodeID;
}
void setSaveNodeID(const NumNodeID saveNodeID)
{
this->saveNodeID = saveNodeID;
}
int getSaveDevice() const
{
return this->saveDevice;
}
void setSaveDevice(const int device)
{
this->saveDevice = device;
}
uint64_t getSaveInode() const
{
return this->saveInode;
}
void setSaveInode(const uint64_t inode)
{
this->saveInode = inode;
}
uint64_t getInternalID() const
{
return this->internalID;
}
bool getIsBuddyMirrored() const { return isBuddyMirrored; }
bool operator< (const FsckDirEntry& other) const
{
if (id < other.id)
return true;
else
return false;
}
bool operator== (const FsckDirEntry& other) const
{
if (id.compare(other.id) != 0)
return false;
else
if (name.compare(other.name) != 0)
return false;
else
if (parentDirID.compare(other.parentDirID) != 0)
return false;
else
if (entryOwnerNodeID != other.entryOwnerNodeID)
return false;
else
if (inodeOwnerNodeID != other.inodeOwnerNodeID)
return false;
else
if (entryType != other.entryType)
return false;
else
if (hasInlinedInode != other.hasInlinedInode)
return false;
else
if (saveNodeID != other.saveNodeID)
return false;
else
if (saveDevice != other.saveDevice)
return false;
else
if (saveInode != other.saveInode)
return false;
else
if (isBuddyMirrored != other.isBuddyMirrored)
return false;
else
return true;
}
bool operator!= (const FsckDirEntry& other) const
{
return !(operator==( other ) );
}
void print()
{
std::cout
<< "id: " << id << std::endl
<< "name: " << name << std::endl
<< "parentDirID: " << parentDirID << std::endl
<< "entryOwnerNodeID: " << entryOwnerNodeID << std::endl
<< "inodeOwnerNodeID: " << inodeOwnerNodeID << std::endl
<< "entryType: " << (int)entryType << std::endl
<< "hasInlinedInode: " << (int)hasInlinedInode << std::endl
<< "saveNodeID: " << saveNodeID << std::endl
<< "saveDevice: " << (int)saveDevice << std::endl
<< "saveInode: " << saveInode << std::endl
<< "isBuddyMirrored: " << isBuddyMirrored << std::endl
<< "internalID: " << internalID << std::endl;
}
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% serdes::stringAlign4(obj->id)
% serdes::stringAlign4(obj->name)
% serdes::stringAlign4(obj->parentDirID)
% obj->entryOwnerNodeID
% obj->inodeOwnerNodeID
% serdes::as<int32_t>(obj->entryType)
% obj->hasInlinedInode
% obj->isBuddyMirrored
% obj->saveNodeID
% obj->saveDevice
% obj->saveInode;
}
};
template<>
struct ListSerializationHasLength<FsckDirEntry> : boost::false_type {};

View File

@@ -0,0 +1,227 @@
#pragma once
#include <common/Common.h>
#include <common/toolkit/FsckTk.h>
#include <common/toolkit/serialization/Serialization.h>
class FsckDirInode;
typedef std::list<FsckDirInode> FsckDirInodeList;
typedef FsckDirInodeList::iterator FsckDirInodeListIter;
class FsckDirInode
{
friend class TestDatabase;
public:
FsckDirInode(const std::string& id, const std::string& parentDirID, NumNodeID parentNodeID,
NumNodeID ownerNodeID, int64_t size, unsigned numHardLinks,
const UInt16Vector& stripeTargets, FsckStripePatternType stripePatternType,
NumNodeID saveNodeID, bool isBuddyMirrored, bool readable, bool isMismirrored) :
id(id), parentDirID(parentDirID), parentNodeID(parentNodeID), ownerNodeID(ownerNodeID),
size(size), numHardLinks(numHardLinks), stripeTargets(stripeTargets),
stripePatternType(stripePatternType), saveNodeID(saveNodeID),
isBuddyMirrored(isBuddyMirrored), readable(readable), isMismirrored(isMismirrored)
{
}
// only for deserialization
FsckDirInode()
{
}
private:
std::string id; // filesystem-wide unique string
std::string parentDirID;
NumNodeID parentNodeID;
NumNodeID ownerNodeID;
int64_t size; // # of subentries
uint32_t numHardLinks;
UInt16Vector stripeTargets;
FsckStripePatternType stripePatternType;
NumNodeID saveNodeID;
bool isBuddyMirrored;
bool readable;
bool isMismirrored;
public:
const std::string& getID() const
{
return id;
}
const std::string& getParentDirID() const
{
return parentDirID;
}
void setParentDirID(const std::string& parentDirID)
{
this->parentDirID = parentDirID;
}
NumNodeID getParentNodeID() const
{
return parentNodeID;
}
void setParentNodeID(NumNodeID parentNodeID)
{
this->parentNodeID = parentNodeID;
}
NumNodeID getOwnerNodeID() const
{
return ownerNodeID;
}
void setOwnerNodeID(NumNodeID ownerNodeID)
{
this->ownerNodeID = ownerNodeID;
}
void setSize(int64_t size)
{
this->size = size;
}
int64_t getSize() const
{
return size;
}
void setNumHardLinks(unsigned numHardLinks)
{
this->numHardLinks = numHardLinks;
}
unsigned getNumHardLinks() const
{
return numHardLinks;
}
UInt16Vector getStripeTargets() const
{
return stripeTargets;
}
UInt16Vector* getStripeTargets()
{
return &stripeTargets;
}
void setStripeTargets(UInt16Vector& stripeTargets)
{
this->stripeTargets = stripeTargets;
}
FsckStripePatternType getStripePatternType() const
{
return stripePatternType;
}
void setStripePatternType(FsckStripePatternType stripePatternType)
{
this->stripePatternType = stripePatternType;
}
bool getReadable() const
{
return readable;
}
void setReadable(bool readable)
{
this->readable = readable;
}
NumNodeID getSaveNodeID() const
{
return saveNodeID;
}
void setSaveNodeID(NumNodeID saveNodeID)
{
this->saveNodeID = saveNodeID;
}
bool getIsBuddyMirrored() const { return isBuddyMirrored; }
bool getIsMismirrored() const { return isMismirrored; }
bool operator<(const FsckDirInode& other) const
{
if ( id < other.getID() )
return true;
else
return false;
}
bool operator==(const FsckDirInode& other) const
{
if ( id.compare(other.getID()) != 0 )
return false;
else
if ( parentDirID.compare(other.getParentDirID()) != 0 )
return false;
else
if ( parentNodeID != other.getParentNodeID() )
return false;
else
if ( ownerNodeID != other.getOwnerNodeID() )
return false;
else
if ( size != other.size )
return false;
else
if ( numHardLinks != other.numHardLinks )
return false;
else
if ( stripeTargets != other.stripeTargets )
return false;
else
if ( stripePatternType != other.stripePatternType )
return false;
else
if ( saveNodeID != other.getSaveNodeID() )
return false;
else
if ( readable != other.getReadable() )
return false;
else
if (isBuddyMirrored != other.isBuddyMirrored)
return false;
else
if (isMismirrored != other.isMismirrored)
return false;
else
return true;
}
bool operator!= (const FsckDirInode& other) const
{
return !(operator==( other ) );
}
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% serdes::stringAlign4(obj->id)
% serdes::stringAlign4(obj->parentDirID)
% obj->parentNodeID
% obj->ownerNodeID
% obj->size
% obj->numHardLinks
% obj->stripeTargets
% serdes::as<int32_t>(obj->stripePatternType)
% obj->saveNodeID
% obj->isBuddyMirrored
% obj->readable
% obj->isMismirrored;
}
};
template<>
struct ListSerializationHasLength<FsckDirInode> : boost::false_type {};

View File

@@ -0,0 +1,91 @@
#pragma once
#include <common/Common.h>
#include <common/toolkit/serialization/Serialization.h>
class FsckDuplicateInodeInfo;
typedef std::vector<FsckDuplicateInodeInfo> FsckDuplicateInodeInfoVector;
typedef FsckDuplicateInodeInfoVector::iterator FsckDuplicateInodeInfoListIter;
/**
*
* class to store inode specific information for duplicate file and directory inode(s)
*/
class FsckDuplicateInodeInfo
{
public:
FsckDuplicateInodeInfo() = default;
FsckDuplicateInodeInfo(
const std::string& id,
const std::string& parentDirId,
uint32_t nodeId,
bool inlined,
bool mirrored,
DirEntryType entryType) :
entryID(id), parentDirID(parentDirId), saveNodeID(nodeId), isInlined(inlined),
isBuddyMirrored(mirrored), dirEntryType(entryType)
{
}
private:
std::string entryID;
std::string parentDirID;
uint32_t saveNodeID;
bool isInlined;
bool isBuddyMirrored;
DirEntryType dirEntryType;
public:
const std::string& getID() const { return entryID; }
const std::string& getParentDirID() const { return parentDirID; }
uint32_t getSaveNodeID() const { return saveNodeID; }
void setSaveNodeID(uint32_t val) { saveNodeID = val; }
bool getIsInlined() const { return isInlined; }
bool getIsBuddyMirrored() const { return isBuddyMirrored; }
DirEntryType getDirEntryType() const { return dirEntryType; }
bool operator<(const FsckDuplicateInodeInfo& other) const
{
return parentDirID < other.parentDirID ||
saveNodeID < other.saveNodeID ||
isInlined < other.isInlined ||
isBuddyMirrored < other.isBuddyMirrored ||
dirEntryType < other.dirEntryType;
}
bool operator==(const FsckDuplicateInodeInfo& other) const
{
return parentDirID == other.parentDirID &&
saveNodeID == other.saveNodeID &&
isInlined == other.isInlined &&
isBuddyMirrored == other.isBuddyMirrored &&
dirEntryType == other.dirEntryType;
}
bool operator!=(const FsckDuplicateInodeInfo& other) const
{
return !(operator==( other ));
}
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% serdes::stringAlign4(obj->entryID)
% serdes::stringAlign4(obj->parentDirID)
% obj->saveNodeID
% obj->isBuddyMirrored
% obj->dirEntryType;
}
};
template<>
struct ListSerializationHasLength<FsckDuplicateInodeInfo> : boost::false_type {};

View File

@@ -0,0 +1,216 @@
#pragma once
#include <common/Common.h>
#include <common/toolkit/FsckTk.h>
#include <common/storage/PathInfo.h>
#include <common/storage/StatData.h>
#include <common/toolkit/serialization/Serialization.h>
class FsckFileInode;
typedef std::list<FsckFileInode> FsckFileInodeList;
typedef FsckFileInodeList::iterator FsckFileInodeListIter;
class FsckFileInode
{
friend class TestDatabase;
public:
FsckFileInode(const std::string& id, const std::string& parentDirID, NumNodeID parentNodeID,
const PathInfo& pathInfo, unsigned userID, unsigned groupID, int64_t fileSize,
unsigned numHardLinks, uint64_t usedBlocks, const UInt16Vector& stripeTargets,
FsckStripePatternType stripePatternType, unsigned chunkSize, NumNodeID saveNodeID,
uint64_t saveInode, int32_t saveDevice, bool isInlined, bool isBuddyMirrored,
bool readable, bool isMismirrored)
{
SettableFileAttribs settableFileAttribs;
settableFileAttribs.userID = userID;
settableFileAttribs.groupID = groupID;
StatData statData(fileSize, &settableFileAttribs, 0, 0, numHardLinks, 0);
initialize(id, parentDirID, parentNodeID, pathInfo, &statData, usedBlocks, stripeTargets,
stripePatternType, chunkSize, saveNodeID, saveInode, saveDevice, isInlined,
isBuddyMirrored, readable, isMismirrored);
}
FsckFileInode(const std::string& id, const std::string& parentDirID, NumNodeID parentNodeID,
const PathInfo& pathInfo, StatData* statData, uint64_t usedBlocks,
const UInt16Vector& stripeTargets, FsckStripePatternType stripePatternType,
unsigned chunkSize, NumNodeID saveNodeID, uint64_t saveInode, int32_t saveDevice,
bool isInlined, bool isBuddyMirrored, bool readable, bool isMismirrored)
{
initialize(id, parentDirID, parentNodeID, pathInfo, statData, usedBlocks, stripeTargets,
stripePatternType, chunkSize, saveNodeID, saveInode, saveDevice, isInlined,
isBuddyMirrored, readable, isMismirrored);
}
FsckFileInode() = default;
private:
std::string id; // filesystem-wide unique string
std::string parentDirID;
NumNodeID parentNodeID;
PathInfo pathInfo;
uint32_t userID;
uint32_t groupID;
uint64_t usedBlocks; // 512byte-blocks
int64_t fileSize; // in byte
uint32_t numHardLinks;
UInt16Vector stripeTargets;
FsckStripePatternType stripePatternType;
uint32_t chunkSize;
NumNodeID saveNodeID; // id of the node, where this inode is saved on
bool isInlined;
bool isBuddyMirrored;
bool readable;
bool isMismirrored;
uint64_t saveInode;
int32_t saveDevice;
void initialize(const std::string& id, const std::string& parentDirID, NumNodeID parentNodeID,
const PathInfo& pathInfo, StatData* statData, uint64_t usedBlocks,
const UInt16Vector& stripeTargets, FsckStripePatternType stripePatternType,
unsigned chunkSize, NumNodeID saveNodeID, uint64_t saveInode, int32_t saveDevice,
bool isInlined, bool isBuddyMirrored, bool readable, bool isMismirrored)
{
this->id = id;
this->parentDirID = parentDirID;
this->parentNodeID = parentNodeID;
this->pathInfo = pathInfo;
/* check pathInfo; deserialization from disk does not set origParentEntryID, if file was
* never moved. On fsck side, we need this set, so we set it to the current parent dir
* here */
if (this->pathInfo.getOrigParentEntryID().empty())
this->pathInfo.setOrigParentEntryID(parentDirID);
this->userID = statData->getUserID();
this->groupID = statData->getGroupID();
this->fileSize = statData->getFileSize();
this->numHardLinks = statData->getNumHardlinks();
this->usedBlocks = usedBlocks;
this->stripeTargets = stripeTargets;
this->stripePatternType = stripePatternType;
this->chunkSize = chunkSize;
this->saveNodeID = saveNodeID;
this->saveInode = saveInode;
this->saveDevice = saveDevice;
this->isInlined = isInlined;
this->isBuddyMirrored = isBuddyMirrored;
this->readable = readable;
this->isMismirrored = isMismirrored;
}
public:
const std::string& getID() const { return id; }
const std::string& getParentDirID() const { return parentDirID; }
NumNodeID getParentNodeID() const { return parentNodeID; }
PathInfo* getPathInfo() { return &(this->pathInfo); }
unsigned getUserID() const { return this->userID; }
unsigned getGroupID() const { return this->groupID; }
int64_t getFileSize() const { return this->fileSize; }
void setFileSize(int64_t fileSize) { this->fileSize = fileSize; }
unsigned getNumHardLinks() const { return this->numHardLinks; }
void setNumHardLinks(unsigned numHardLinks) { this->numHardLinks = numHardLinks; }
uint64_t getUsedBlocks() const { return this->usedBlocks; }
const UInt16Vector& getStripeTargets() const { return stripeTargets; }
void setStripeTargets(const UInt16Vector& targets) { stripeTargets = targets; }
FsckStripePatternType getStripePatternType() const { return stripePatternType; }
unsigned getChunkSize() const { return chunkSize; }
NumNodeID getSaveNodeID() const { return saveNodeID; }
bool getIsInlined() const { return isInlined; }
void setIsInlined(bool value) { isInlined = value; }
bool getIsBuddyMirrored() const { return isBuddyMirrored; }
bool getReadable() const { return readable; }
uint64_t getSaveInode() const { return this->saveInode; }
int32_t getSaveDevice() const { return this->saveDevice; }
bool getIsMismirrored() const { return isMismirrored; }
bool operator<(const FsckFileInode& other) const
{
return id < other.id;
}
bool operator==(const FsckFileInode& other) const
{
return id == other.id &&
parentDirID == other.parentDirID &&
parentNodeID == other.parentNodeID &&
userID == other.userID &&
groupID == other.groupID &&
fileSize == other.fileSize &&
numHardLinks == other.numHardLinks &&
usedBlocks == other.usedBlocks &&
stripeTargets == other.stripeTargets &&
stripePatternType == other.stripePatternType &&
chunkSize == other.chunkSize &&
saveNodeID == other.saveNodeID &&
saveInode == other.saveInode &&
saveDevice == other.saveDevice &&
isInlined == other.isInlined &&
isBuddyMirrored == other.isBuddyMirrored &&
readable == other.readable &&
isMismirrored == other.isMismirrored;
}
bool operator!= (const FsckFileInode& other) const
{
return !(*this == other);
}
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% serdes::stringAlign4(obj->id)
% serdes::stringAlign4(obj->parentDirID)
% obj->parentNodeID
% obj->pathInfo
% obj->userID
% obj->groupID
% obj->fileSize
% obj->numHardLinks
% obj->usedBlocks
% obj->stripeTargets
% serdes::as<int32_t>(obj->stripePatternType)
% obj->chunkSize
% obj->saveNodeID
% obj->saveInode
% obj->saveDevice
% obj->isInlined
% obj->isBuddyMirrored
% obj->readable
% obj->isMismirrored;
}
};
template<>
struct ListSerializationHasLength<FsckFileInode> : boost::false_type {};

View File

@@ -0,0 +1,122 @@
#pragma once
#include <common/Common.h>
#include <common/nodes/NumNodeID.h>
#include <common/toolkit/serialization/Serialization.h>
class FsckFsID;
typedef std::list<FsckFsID> FsckFsIDList;
typedef FsckFsIDList::iterator FsckFsIDListIter;
class FsckFsID
{
friend class TestDatabase;
private:
std::string id;
std::string parentDirID;
NumNodeID saveNodeID;
int32_t saveDevice;
uint64_t saveInode;
bool isBuddyMirrored;
public:
/*
* @param id the entryID
* @param parentDirID id of the parent dir
* @param saveNodeID the id of the node, on which the fsid file is saved
* @param saveDevice the underlying device, on which the file is saved
* @param saveInode the underlying inode, which holds the file
* */
FsckFsID(const std::string& id, const std::string& parentDirID, NumNodeID saveNodeID,
int saveDevice, uint64_t saveInode, bool isBuddyMirrored) :
id(id), parentDirID(parentDirID), saveNodeID(saveNodeID), saveDevice(saveDevice),
saveInode(saveInode), isBuddyMirrored(isBuddyMirrored)
{
}
// only for deserialization!
FsckFsID()
{
}
const std::string& getID() const
{
return this->id;
}
const std::string& getParentDirID() const
{
return this->parentDirID;
}
NumNodeID getSaveNodeID() const
{
return this->saveNodeID;
}
int getSaveDevice() const
{
return this->saveDevice;
}
uint64_t getSaveInode() const
{
return this->saveInode;
}
bool getIsBuddyMirrored() const { return isBuddyMirrored; }
bool operator<(const FsckFsID& other) const
{
if ( id < other.id )
return true;
else
return false;
}
bool operator==(const FsckFsID& other) const
{
if ( id.compare(other.id) )
return false;
else
if ( parentDirID.compare(other.parentDirID) )
return false;
else
if ( saveNodeID != other.saveNodeID )
return false;
else
if ( saveDevice != other.saveDevice )
return false;
else
if ( saveInode != other.saveInode )
return false;
else
if (isBuddyMirrored != other.isBuddyMirrored)
return false;
else
return true;
}
bool operator!= (const FsckFsID& other) const
{
return !(operator==( other ) );
}
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% serdes::stringAlign4(obj->id)
% serdes::stringAlign4(obj->parentDirID)
% obj->saveNodeID
% obj->saveDevice
% obj->saveInode
% obj->isBuddyMirrored;
}
};
template<>
struct ListSerializationHasLength<FsckFsID> : boost::false_type {};

View File

@@ -0,0 +1,84 @@
#pragma once
#include <common/Common.h>
#include <common/toolkit/MetadataTk.h>
#include <common/toolkit/serialization/Serialization.h>
class FsckModificationEvent;
typedef std::list<FsckModificationEvent> FsckModificationEventList;
typedef FsckModificationEventList::iterator FsckModificationEventListIter;
class FsckModificationEvent
{
friend class TestDatabase;
private:
ModificationEventType eventType;
std::string entryID;
public:
/*
* @param eventType
* @param entryID
*/
FsckModificationEvent(ModificationEventType eventType, const std::string& entryID):
eventType(eventType), entryID(entryID)
{
}
// only for deserialization!
FsckModificationEvent()
{
}
// getter/setter
ModificationEventType getEventType() const
{
return this->eventType;
}
const std::string& getEntryID() const
{
return this->entryID;
}
bool operator<(const FsckModificationEvent& other) const
{
if ( eventType < other.eventType )
return true;
else
if ( ( eventType == other.eventType ) && ( entryID < other.entryID ))
return true;
else
return false;
}
bool operator==(const FsckModificationEvent& other) const
{
if ( eventType != other.eventType )
return false;
else
if ( entryID.compare(other.entryID) )
return false;
else
return true;
}
bool operator!= (const FsckModificationEvent& other) const
{
return !(operator==( other ) );
}
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% serdes::as<uint8_t>(obj->eventType)
% serdes::stringAlign4(obj->entryID);
}
};
template<>
struct ListSerializationHasLength<FsckModificationEvent> : boost::false_type {};

View File

@@ -0,0 +1,78 @@
#pragma once
#include <common/Common.h>
#include <common/toolkit/serialization/Serialization.h>
enum FsckTargetIDType
{
FsckTargetIDType_TARGET = 0,
FsckTargetIDType_BUDDYGROUP = 1
};
class FsckTargetID
{
private:
uint16_t id;
FsckTargetIDType targetIDType;
public:
FsckTargetID(uint16_t id, FsckTargetIDType targetIDType) : id(id), targetIDType(targetIDType)
{
// all initialization done in initialization list
};
//only for deserialization
FsckTargetID() {}
uint16_t getID() const
{
return id;
}
FsckTargetIDType getTargetIDType() const
{
return targetIDType;
}
bool operator< (const FsckTargetID& other) const
{
if (id < other.id)
return true;
if (id == other.id && targetIDType < other.targetIDType)
return true;
return false;
}
bool operator== (const FsckTargetID& other) const
{
if (id != other.id)
return false;
else
if (targetIDType != other.targetIDType)
return false;
else
return true;
}
bool operator!= (const FsckTargetID& other) const
{
return !(operator==( other ) );
}
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% obj->id
% serdes::as<int32_t>(obj->targetIDType);
}
};
typedef std::list<FsckTargetID> FsckTargetIDList;
typedef FsckTargetIDList::iterator FsckTargetIDListIter;
template<>
struct ListSerializationHasLength<FsckTargetID> : boost::false_type {};

View File

@@ -0,0 +1,53 @@
#pragma once
/**
* Helper class to easily log a fragment of the current backtrace inline.
*/
#include <execinfo.h>
#include <memory>
#include <sstream>
template<unsigned LEN>
class Backtrace
{
public:
Backtrace() __attribute__((noinline))
{
void* btbuf[LEN + 2];
auto btlen = backtrace(&btbuf[0], LEN + 2);
std::unique_ptr<char*, free_delete<char*>> symbols(backtrace_symbols(&btbuf[0], btlen));
if (!symbols || btlen < 2)
{
bt = "(error)";
}
else
{
std::ostringstream oss;
for (int i = 2; i < btlen - 1; i++)
oss << symbols.get()[i] << "; ";
oss << symbols.get()[btlen - 1] << ".";
bt = oss.str();
}
}
const std::string& str() const { return bt; }
private:
std::string bt;
template<class T> struct free_delete { void operator()(T* p) { free(p); } };
};
template<>
class Backtrace<0> { public: Backtrace() = delete; };
template<unsigned LEN>
std::ostream& operator<<(std::ostream& os, const Backtrace<LEN>& bt)
{
os << bt.str();
return os;
}

View File

@@ -0,0 +1,80 @@
#pragma once
#include "Slice.h"
#include <cstdlib>
#include <utility>
namespace
{
// A simple (maybe overly simple) class that owns a heap-allocated buffer.
// There is no size (only capacity), no contained type, no pluggable allocator.
// Also no exceptions.
// NOTE: allocation happens in the reset() function. You must check the return value.
class MallocBuffer
{
void *mData = 0;
size_t mCapacity = 0;
public:
void *data() const
{
return mData;
}
size_t capacity() const
{
return mCapacity;
}
void drop()
{
std::free(mData);
mData = 0;
mCapacity = 0;
}
[[nodiscard]] bool reset(size_t capacity)
{
drop();
mData = std::malloc(capacity);
if (! mData)
return false;
mCapacity = capacity;
return true;
}
Slice as_slice() const
{
return Slice(mData, mCapacity);
}
// Avoid accidental copies
MallocBuffer(MallocBuffer const& other) = delete;
MallocBuffer& operator=(MallocBuffer const& other) = delete;
MallocBuffer& operator=(MallocBuffer&& other)
{
drop();
std::swap(mData, other.mData);
std::swap(mCapacity, other.mCapacity);
return *this;
}
MallocBuffer(MallocBuffer&& other)
{
std::swap(mData, other.mData);
std::swap(mCapacity, other.mCapacity);
}
MallocBuffer() = default;
~MallocBuffer()
{
drop();
}
};
}

View File

@@ -0,0 +1,100 @@
#pragma once
#include "String.h"
namespace
{
// Similar to MallocBuffer, but for strings.
// We tolerate a bit of boilerplate and duplication in exchange for API simplicity.
// The implementation currently returns a zero-sized string as a pointer to a
// statically allocated 0 byte (a "" literal). We still store an NULL pointer
// internally and check the case on .get(). This may not seem ideal but it
// simplifies the code. There might be a better solution.
class MallocString
{
char *mData = 0;
size_t mSizeBytes = 0;
public:
const char *data() const
{
return mData ? mData : "";
}
size_t sizeBytes() const
{
return mSizeBytes;
}
size_t sizeBytes0() const
{
return mSizeBytes + 1;
}
operator String() const
{
return String(mData, mSizeBytes);
}
operator StringZ() const
{
return StringZ::fromZeroTerminated(data(), mSizeBytes);
}
void drop()
{
std::free(mData);
mData = 0;
mSizeBytes = 0;
}
[[nodiscard]] bool reset(const char *data, size_t sizeBytes)
{
drop();
mData = (char *) std::malloc(sizeBytes + 1);
if (! mData)
return false;
mSizeBytes = sizeBytes;
memcpy(mData, data, sizeBytes);
mData[sizeBytes] = 0;
return true;
}
[[nodiscard]] bool reset(String string)
{
return reset(string.data(), string.sizeBytes());
}
[[nodiscard]] bool reset(StringZ string)
{
return reset(string.data(), string.sizeBytes());
}
// Avoid accidental copies
MallocString(MallocString const& other) = delete;
MallocString& operator=(MallocString const& other) = delete;
MallocString& operator=(MallocString&& other)
{
drop();
std::swap(mData, other.mData);
std::swap(mSizeBytes, other.mSizeBytes);
return *this;
}
MallocString(MallocString&& other)
{
std::swap(mData, other.mData);
std::swap(mSizeBytes, other.mSizeBytes);
}
MallocString() = default;
~MallocString()
{
drop();
}
};
}

View File

@@ -0,0 +1,140 @@
#pragma once
#include <assert.h>
#include <string.h>
// Basic generic memory region handling.
// There are also read-only and write-only variants.
// Added type safety because of automatic bounds checks and convenient copy functions.
namespace
{
class Slice;
class RO_Slice
{
const void *mData = nullptr;
size_t mSize = 0;
public:
const void *data() const
{
return mData;
}
size_t sizeBytes() const
{
return mSize;
}
RO_Slice offsetBytes(size_t offBytes) const
{
assert(offBytes <= mSize);
return RO_Slice((char *) mData + offBytes, mSize - offBytes);
}
RO_Slice limitBytes(size_t sizeBytes) const
{
assert(sizeBytes <= mSize);
return RO_Slice(mData, mSize - sizeBytes);
}
RO_Slice() = default;
RO_Slice(const void *data, size_t sizeBytes)
{
mData = data;
mSize = sizeBytes;
}
};
class WO_Slice
{
void *mData = nullptr;
size_t mSize = 0;
public:
// even though this is WO, we still return the data -- it would be unnecessarily unergonomic to not offer it back.
// Nevertheless the WO_Slice is less capable than a full Slice -- we can't easily make an RO_Slice out of it for example.
void *data() const
{
return mData;
}
size_t sizeBytes() const
{
return mSize;
}
WO_Slice offsetBytes(size_t offBytes) const
{
assert(offBytes <= mSize);
return WO_Slice((char *) mData + offBytes, mSize - offBytes);
}
WO_Slice limitBytes(size_t sizeBytes) const
{
assert(sizeBytes <= mSize);
return WO_Slice(mData, mSize - sizeBytes);
}
WO_Slice() = default;
WO_Slice(void *data, size_t sizeBytes)
{
mData = data;
mSize = sizeBytes;
}
};
class Slice
{
void *mData = nullptr;
size_t mSize = 0;
public:
void *data() const
{
return mData;
}
size_t sizeBytes() const
{
return mSize;
}
Slice offsetBytes(size_t offBytes) const
{
assert(offBytes <= mSize);
return Slice((char *) mData + offBytes, mSize - offBytes);
}
Slice limitBytes(size_t sizeBytes) const
{
assert(sizeBytes <= mSize);
return Slice(mData, mSize - sizeBytes);
}
operator WO_Slice() const
{
return WO_Slice(mData, mSize);
}
operator RO_Slice() const
{
return RO_Slice(mData, mSize);
}
Slice() = default;
Slice(void *data, size_t sizeBytes)
{
mData = data;
mSize = sizeBytes;
}
};
static inline void sliceFill0(WO_Slice dest)
{
if (dest.sizeBytes() > 0) // passing NULL to memset() leads to UB according to standard.
memset(dest.data(), 0, dest.sizeBytes());
}
// A copy function that copies all the source slice. Source slice must be <= destination slice.
// This will probably the most commonly used function.
static inline void sliceCopy(WO_Slice dest, RO_Slice source)
{
assert(source.sizeBytes() <= dest.sizeBytes());
memcpy(dest.data(), source.data(), source.sizeBytes());
}
// A variant that copies as much as possible from input to output.
static inline void sliceCopyFill0(WO_Slice dest, RO_Slice source)
{
sliceCopy(dest, source);
sliceFill0(dest.offsetBytes(source.sizeBytes()));
}
}

View File

@@ -0,0 +1,169 @@
#pragma once
#include <string.h>
#include "Slice.h"
namespace
{
// String slice type, not necessarily zero-terminated. It's basically like
// Slice but it comes with the expectation to contain ASCII/UTF-8 test. It is
// char-typed and has just a tiny bit of helper functionality attached
// (equals/startswith/endswith). It converts easily to Slice using slice()
// method.
class String
{
const char *mData = nullptr;
size_t mSizeBytes = 0;
public:
const char *data() const
{
return mData;
}
size_t sizeBytes() const
{
return mSizeBytes;
}
String offsetBytes(size_t offsetBytes) const
{
if (offsetBytes > mSizeBytes)
offsetBytes = mSizeBytes;
return String(mData + offsetBytes, mSizeBytes - offsetBytes);
}
String limitBytes(size_t sizeBytes) const
{
if (sizeBytes >= mSizeBytes)
sizeBytes = mSizeBytes;
return String(mData, sizeBytes);
}
bool equals(String other) const
{
if (mSizeBytes != other.mSizeBytes)
return false;
return !memcmp(mData, other.mData, mSizeBytes);
}
bool startswith(String other) const
{
if (mSizeBytes < other.mSizeBytes)
return false;
return limitBytes(other.mSizeBytes).equals(other);
}
bool endswith(String other) const
{
if (mSizeBytes < other.mSizeBytes)
return false;
return offsetBytes(mSizeBytes - other.mSizeBytes).equals(other);
}
RO_Slice slice() const
{
return RO_Slice(mData, mSizeBytes);
};
operator RO_Slice() const
{
return RO_Slice(mData, mSizeBytes);
}
String() = default;
String(const char *s, size_t sizeBytes)
{
mData = s;
mSizeBytes = sizeBytes;
};
};
// A zero-terminated string slice type.
// It is non-allocating and non-owning, storing pointer + length pair internally.
// The contract is that it must be a C-string of the given size i.e. it's
// sizeBytes()th byte must be zero.
class StringZ
{
const char *mData = "";
size_t mSizeBytes = 0; // number of bytes, excluding the terminating NUL.
public:
const char *data() const
{
return mData;
}
const char *c_str() const
{
return mData;
}
size_t sizeBytes() const
{
return mSizeBytes;
}
size_t sizeBytesZ() const // size including terminating zero
{
return mSizeBytes + 1;
}
StringZ offsetBytes(size_t offsetBytes) const
{
if (offsetBytes > mSizeBytes)
offsetBytes = mSizeBytes;
return StringZ::fromZeroTerminated(mData + offsetBytes, mSizeBytes - offsetBytes);
}
// NOTE: the result is not zero-terminated, so we can only return String!
String limitBytes(size_t sizeBytes) const
{
if (sizeBytes >= mSizeBytes)
sizeBytes = mSizeBytes;
return String(mData, sizeBytes);
}
RO_Slice slice() const
{
return RO_Slice(mData, mSizeBytes);
}
RO_Slice sliceZ() const
{
return RO_Slice(mData, mSizeBytes + 1);
}
String string() const
{
return String(mData, mSizeBytes);
}
operator String() const
{
return String(mData, mSizeBytes);
}
bool equals(StringZ other) const
{
return string().equals(other.string());
}
bool startswith(StringZ other) const
{
return string().startswith(other.string());
}
bool endswith(StringZ other) const
{
return string().endswith(other.string());
}
StringZ() = default;
explicit StringZ(const char *data)
{
mData = data;
mSizeBytes = strlen(data);
}
static StringZ fromZeroTerminated(const char *data, size_t sizeBytes)
{
StringZ out;
out.mData = data;
out.mSizeBytes = sizeBytes;
return out;
}
};
// We can produce String and StringZ literals using "Hello"_s and "Hello"_sz syntax.
inline String operator ""_s(const char *lit, size_t size)
{
return String(lit, size);
}
inline StringZ operator ""_sz(const char *lit, size_t size)
{
return StringZ::fromZeroTerminated(lit, size);
}
}

View File

@@ -0,0 +1,95 @@
#include <common/app/log/LogContext.h>
#include <common/net/message/NetMessageLogHelper.h>
#include "AbstractNetMessageFactory.h"
#include "SimpleMsg.h"
/**
* Create NetMessage object (specific type determined by msg header) from a raw msg buffer.
*
* @return (msg->msgType is NETMSGTYPE_Invalid on error)
*/
std::unique_ptr<NetMessage> AbstractNetMessageFactory::createFromRaw(char* recvBuf,
size_t bufLen) const
{
if(unlikely(bufLen < NETMSG_MIN_LENGTH))
return boost::make_unique<SimpleMsg>(NETMSGTYPE_Invalid);
NetMessageHeader header;
// decode the message header
NetMessage::deserializeHeader(recvBuf, bufLen, &header);
// delegate the rest of the work to another method...
char* msgPayloadBuf = recvBuf + NETMSG_HEADER_LENGTH;
size_t msgPayloadBufLen = bufLen - NETMSG_HEADER_LENGTH;
return createFromPreprocessedBuf(&header, msgPayloadBuf, msgPayloadBufLen);
}
/**
* Create NetMessage object (specific type determined by msg header) from a raw msg payload buffer,
* for which the msg header has already been deserialized.
*
* @return (msg->msgType is NETMSGTYPE_Invalid on error)
*/
std::unique_ptr<NetMessage> AbstractNetMessageFactory::createFromPreprocessedBuf(
NetMessageHeader* header, char* msgPayloadBuf, size_t msgPayloadBufLen) const
{
const char* logContext = "NetMsgFactory (create msg from buf)";
// create the message object for the given message type
std::unique_ptr<NetMessage> msg(createFromMsgType(header->msgType));
if(unlikely(msg->getMsgType() == NETMSGTYPE_Invalid) )
{
LogContext(logContext).log(Log_NOTICE,
"Received an invalid or unhandled message. "
"Message type (from raw header): " + netMessageTypeToStr(header->msgType));
return msg;
}
// apply message feature flags and check compatibility
msg->setMsgHeaderFeatureFlags(header->msgFeatureFlags);
bool checkCompatRes = msg->checkHeaderFeatureFlagsCompat();
if(unlikely(!checkCompatRes) )
{ // incompatible feature flag was set => log error with msg type
LogContext(logContext).log(Log_WARNING,
"Received a message with incompatible feature flags. "
"Message type: " + netMessageTypeToStr(header->msgType) + "; "
"Flags (hex): " + StringTk::uintToHexStr(msg->getMsgHeaderFeatureFlags() ) );
return boost::make_unique<SimpleMsg>(NETMSGTYPE_Invalid);
}
// check whether the header flags are as we expect them:
// * if the message does not support mirroring, header flags must be 0
// * otherwise, they must not contain flags that are not defined
if (header->msgFlags & ~(msg->supportsMirroring() ? NetMessageHeader::FlagsMask : 0))
{
LOG(GENERAL, WARNING, "Received a message with invalid header flags", header->msgType,
header->msgFlags);
return boost::make_unique<SimpleMsg>(NETMSGTYPE_Invalid);
}
msg->msgHeader = *header;
// deserialize message payload
bool deserRes = msg->deserializePayload(msgPayloadBuf, msgPayloadBufLen);
if(unlikely(!deserRes) )
{ // deserialization failed => log error with msg type
LogContext(logContext).log(Log_NOTICE,
"Failed to decode message. "
"Message type: " + netMessageTypeToStr(header->msgType));
return boost::make_unique<SimpleMsg>(NETMSGTYPE_Invalid);
}
return msg;
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include <common/net/message/NetMessage.h>
class AbstractNetMessageFactory
{
public:
virtual ~AbstractNetMessageFactory() {}
std::unique_ptr<NetMessage> createFromRaw(char* recvBuf, size_t bufLen) const;
std::unique_ptr<NetMessage> createFromPreprocessedBuf(NetMessageHeader* header,
char* msgPayloadBuf, size_t msgPayloadBufLen) const;
std::unique_ptr<NetMessage> createFromBuf(std::vector<char> buf) const
{
auto result = createFromRaw(&buf[0], buf.size());
if (result)
result->backingBuffer = std::move(buf);
return result;
}
protected:
AbstractNetMessageFactory() {};
virtual std::unique_ptr<NetMessage> createFromMsgType(unsigned short msgType) const = 0;
};

View File

@@ -0,0 +1,87 @@
#pragma once
#include "NetMessage.h"
#include <common/net/message/control/AckMsg.h>
// Ack messages are used for request that might not be answered immediately. For example
// UDP tranfers or file locks.
class AcknowledgeableMsg : public NetMessage
{
protected:
AcknowledgeableMsg(unsigned short msgType, const char* ackID)
: NetMessage(msgType), ackID(ackID), ackIDLen(strlen(ackID) )
{
}
void serializeAckID(Serializer& ser, unsigned align = 1) const
{
ser % serdes::rawString(ackID, ackIDLen, align);
}
void serializeAckID(Deserializer& des, unsigned align = 1)
{
des % serdes::rawString(ackID, ackIDLen, align);
}
private:
const char* ackID;
unsigned ackIDLen;
public:
// setters & getters
const char* getAckID() const { return ackID; }
bool wantsAck() const { return ackIDLen != 0; }
/**
* @param ackID just a reference
*/
void setAckID(const char* ackID)
{
this->ackID = ackID;
this->ackIDLen = strlen(ackID);
}
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
obj->serializeAckID(ctx);
}
bool acknowledge(ResponseContext& ctx)
{
if(ackIDLen == 0)
return false;
ctx.sendResponse(AckMsg(ackID) );
return true;
}
};
template<typename Derived>
class AcknowledgeableMsgSerdes : public AcknowledgeableMsg
{
protected:
typedef AcknowledgeableMsgSerdes BaseType;
AcknowledgeableMsgSerdes(unsigned short msgType, const char* ackID = "")
: AcknowledgeableMsg(msgType, ackID)
{
}
protected:
void serializePayload(Serializer& ser) const
{
ser % *(Derived*) this;
}
bool deserializePayload(const char* buf, size_t bufLen)
{
Deserializer des(buf, bufLen);
des % *(Derived*) this;
return des.good();
}
};

View File

@@ -0,0 +1,447 @@
#pragma once
#include <common/net/sock/NetworkInterfaceCard.h>
#include <common/net/sock/Socket.h>
#include <common/toolkit/HighResolutionStats.h>
#include <common/toolkit/serialization/Serialization.h>
#include <common/Common.h>
#include "NetMessageLogHelper.h"
#include "NetMessageTypes.h"
#include <climits>
// common message constants
// ========================
#define NETMSG_MIN_LENGTH NETMSG_HEADER_LENGTH
#define NETMSG_HEADER_LENGTH 40 /* length of the header (see struct NetMessageHeader) */
#define NETMSG_MAX_MSG_SIZE 65536 // 64kB
#define NETMSG_MAX_PAYLOAD_SIZE ((unsigned)(NETMSG_MAX_MSG_SIZE - NETMSG_HEADER_LENGTH))
#define NETMSG_DEFAULT_USERID (~0) // non-zero to avoid mixing up with root userID
struct NetMessageHeader
{
static constexpr const uint64_t MSG_PREFIX = (0x42474653ULL << 32) + BEEGFS_DATA_VERSION;
static const uint8_t Flag_BuddyMirrorSecond = 0x01;
static const uint8_t Flag_IsSelectiveAck = 0x02;
static const uint8_t Flag_HasSequenceNumber = 0x04;
static const uint8_t FlagsMask = 0x07;
uint32_t msgLength; // in bytes
uint16_t msgFeatureFlags; // feature flags for derived messages (depend on msgType)
uint8_t msgCompatFeatureFlags;
uint8_t msgFlags;
uint16_t msgType; // the type of payload, defined as NETMSGTYPE_x
uint16_t msgTargetID; // targetID (not groupID) for per-target workers on storage server
uint32_t msgUserID; // system user ID for per-user msg queues, stats etc.
uint64_t msgSequence; // for retries, 0 if not present
uint64_t msgSequenceDone; // a sequence number that has been fully processed, or 0
static void fixLengthField(Serializer& ser, uint32_t actualLength)
{
ser % actualLength;
}
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
uint64_t prefix = MSG_PREFIX;
ctx
% obj->msgLength
% obj->msgFeatureFlags
% obj->msgCompatFeatureFlags
% obj->msgFlags
% prefix
% obj->msgType
% obj->msgTargetID
% obj->msgUserID
% obj->msgSequence
% obj->msgSequenceDone;
checkPrefix(ctx, prefix);
}
static void checkPrefix(Deserializer& des, uint64_t prefix)
{
if (unlikely(!des.good() ) || prefix != MSG_PREFIX)
des.setBad();
}
static void checkPrefix(Serializer& ser, uint64_t prefix)
{
}
static unsigned extractMsgLengthFromBuf(const char* recvBuf, unsigned bufLen)
{
Deserializer des(recvBuf, bufLen);
uint32_t length;
des % length;
// return -1 if the buffer was short, which will also be larger than any possible message
// we can possibly receive (due to 16bit message length)
return des.good() ? length : -1;
}
};
class NetMessage
{
friend class AbstractNetMessageFactory;
friend class TestMsgSerializationBase;
public:
virtual ~NetMessage() {}
static void deserializeHeader(char* buf, size_t bufLen, NetMessageHeader* outHeader)
{
Deserializer des(buf, bufLen);
des % *outHeader;
if(unlikely(!des.good()))
outHeader->msgType = NETMSGTYPE_Invalid;
}
class ResponseContext
{
friend class NetMessage;
public:
ResponseContext(struct sockaddr_in* fromAddr, Socket* sock, char* respBuf,
unsigned bufLen, HighResolutionStats* stats, bool locallyGenerated = false)
: fromAddr(fromAddr), socket(sock), responseBuffer(respBuf),
responseBufferLength(bufLen), stats(stats), locallyGenerated(locallyGenerated)
{}
void sendResponse(const NetMessage& response) const
{
unsigned msgLength =
response.serializeMessage(responseBuffer, responseBufferLength).second;
socket->sendto(responseBuffer, msgLength, 0,
reinterpret_cast<const sockaddr*>(fromAddr), sizeof(*fromAddr) );
}
HighResolutionStats* getStats() const { return stats; }
Socket* getSocket() const { return socket; }
char* getBuffer() const { return responseBuffer; }
unsigned getBufferLength() const { return responseBufferLength; }
std::string peerName() const
{
return fromAddr ? Socket::ipaddrToStr(fromAddr->sin_addr) : socket->getPeername();
}
bool isLocallyGenerated() const { return locallyGenerated; }
private:
struct sockaddr_in* fromAddr;
Socket* socket;
char* responseBuffer;
unsigned responseBufferLength;
HighResolutionStats* stats;
bool locallyGenerated;
};
/**
* Processes this incoming message.
*
* Note: Some messages might be received over a datagram socket, so the response
* must be atomic (=> only a single sendto()-call)
*
* @param fromAddr must be NULL for stream sockets
* @return false if the client should be disconnected (e.g. because it sent invalid data)
* @throw SocketException on communication error
*/
virtual bool processIncoming(ResponseContext& ctx)
{
// Note: Has to be implemented appropriately by derived classes.
// Empty implementation provided here for invalid messages and other messages
// that don't require this way of processing (e.g. some response messages).
return false;
}
/**
* Returns all feature flags that are supported by this message. Defaults to "none", so this
* method needs to be overridden by derived messages that actually support header feature
* flags.
*
* @return combination of all supported feature flags
*/
virtual unsigned getSupportedHeaderFeatureFlagsMask() const
{
return 0;
}
virtual bool supportsMirroring() const { return false; }
protected:
NetMessage(unsigned short msgType)
{
this->msgHeader.msgLength = 0;
this->msgHeader.msgFeatureFlags = 0;
this->msgHeader.msgCompatFeatureFlags = 0;
this->msgHeader.msgFlags = 0;
this->msgHeader.msgType = msgType;
this->msgHeader.msgUserID = NETMSG_DEFAULT_USERID;
this->msgHeader.msgTargetID = 0;
this->msgHeader.msgSequence = 0;
this->msgHeader.msgSequenceDone = 0;
this->releaseSockAfterProcessing = true;
}
virtual void serializePayload(Serializer& ser) const = 0;
virtual bool deserializePayload(const char* buf, size_t bufLen) = 0;
private:
NetMessageHeader msgHeader;
bool releaseSockAfterProcessing; /* false if sock was already released during
processIncoming(), e.g. due to early response. */
std::vector<char> backingBuffer;
public:
std::pair<bool, unsigned> serializeMessage(char* buf, size_t bufLen) const
{
Serializer ser(buf, bufLen);
Serializer atStart = ser.mark();
ser % msgHeader;
serializePayload(ser);
// fix message length in header and serialize header again to fix the message length
NetMessageHeader::fixLengthField(atStart, ser.size() );
return std::make_pair(ser.good(), ser.size() );
}
/**
* Check if the msg sender has set an incompatible feature flag.
*
* @return false if an incompatible feature flag was set
*/
bool checkHeaderFeatureFlagsCompat() const
{
unsigned unsupportedFlags = ~getSupportedHeaderFeatureFlagsMask();
if(unlikely(msgHeader.msgFeatureFlags & unsupportedFlags) )
return false; // an unsupported flag was set
return true;
}
// getters & setters
unsigned short getMsgType() const
{
return msgHeader.msgType;
}
/**
* Note: calling this method is expensive, so use it only in error/debug code paths.
*
* @return human-readable message type (intended for log messages).
*/
std::string getMsgTypeStr() const
{
return netMessageTypeToStr(msgHeader.msgType);
}
unsigned getMsgHeaderFeatureFlags() const
{
return msgHeader.msgFeatureFlags;
}
/**
* Note: You probably rather want to call addMsgHeaderFeatureFlag() instead of this.
*/
void setMsgHeaderFeatureFlags(unsigned msgFeatureFlags)
{
this->msgHeader.msgFeatureFlags = msgFeatureFlags;
}
/**
* Test flag. (For convenience and readability.)
*
* @return true if given flag is set.
*/
bool isMsgHeaderFeatureFlagSet(unsigned flag) const
{
return (this->msgHeader.msgFeatureFlags & flag) != 0;
}
/**
* Add another flag without clearing the previously set flags.
*
* Note: The receiver will reject this message if it doesn't know the given feature flag.
*/
void addMsgHeaderFeatureFlag(unsigned flag)
{
this->msgHeader.msgFeatureFlags |= flag;
}
/**
* Remove a certain flag without clearing the other previously set flags.
*/
void unsetMsgHeaderFeatureFlag(unsigned flag)
{
this->msgHeader.msgFeatureFlags &= ~flag;
}
uint8_t getMsgHeaderCompatFeatureFlags() const
{
return msgHeader.msgCompatFeatureFlags;
}
void setMsgHeaderCompatFeatureFlags(uint8_t msgCompatFeatureFlags)
{
this->msgHeader.msgCompatFeatureFlags = msgCompatFeatureFlags;
}
/**
* Test flag. (For convenience and readability.)
*
* @return true if given flag is set.
*/
bool isMsgHeaderCompatFeatureFlagSet(uint8_t flag) const
{
return (this->msgHeader.msgCompatFeatureFlags & flag) != 0;
}
/**
* Add another flag without clearing the previously set flags.
*
* Note: "compat" means these flags might not be understood and will then just be ignored by
* the receiver (e.g. if the receiver is an older fhgfs version).
*/
void addMsgHeaderCompatFeatureFlag(uint8_t flag)
{
this->msgHeader.msgCompatFeatureFlags |= flag;
}
unsigned getMsgHeaderUserID() const
{
return msgHeader.msgUserID;
}
void setMsgHeaderUserID(unsigned userID)
{
this->msgHeader.msgUserID = userID;
}
unsigned getMsgHeaderTargetID() const
{
return msgHeader.msgTargetID;
}
/**
* @param targetID this has to be an actual targetID (not a groupID); (default is 0 if
* targetID is not applicable).
*/
void setMsgHeaderTargetID(uint16_t targetID)
{
this->msgHeader.msgTargetID = targetID;
}
bool getReleaseSockAfterProcessing() const
{
return releaseSockAfterProcessing;
}
void setReleaseSockAfterProcessing(bool releaseSockAfterProcessing)
{
this->releaseSockAfterProcessing = releaseSockAfterProcessing;
}
uint64_t getSequenceNumber() const { return msgHeader.msgSequence; }
void setSequenceNumber(uint64_t value) { msgHeader.msgSequence = value; }
uint64_t getSequenceNumberDone() const { return msgHeader.msgSequenceDone; }
void setSequenceNumberDone(uint64_t value) { msgHeader.msgSequenceDone = value; }
uint32_t getLength() const { return msgHeader.msgLength; }
uint8_t getFlags() const { return msgHeader.msgFlags; }
bool hasFlag(uint8_t flag) const { return msgHeader.msgFlags & flag; }
void addFlag(uint8_t flag) { msgHeader.msgFlags |= flag; }
void removeFlag(uint8_t flag) { msgHeader.msgFlags &= ~flag; }
};
template<typename Derived>
class NetMessageSerdes : public NetMessage
{
protected:
typedef NetMessageSerdes<Derived> BaseType;
NetMessageSerdes(unsigned short msgType)
: NetMessage(msgType)
{}
void serializePayload(Serializer& ser) const
{
ser % *(Derived*) this;
}
bool deserializePayload(const char* buf, size_t bufLen)
{
Deserializer des(buf, bufLen);
des % *(Derived*) this;
return des.good();
}
};
template<typename Derived>
class MirroredMessageBase : public NetMessage
{
public:
std::pair<NodeType, NumNodeID> getRequestorID(NetMessage::ResponseContext& ctx) const
{
if (this->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond))
return {requestorNodeType, requestorNodeID};
else
return {ctx.getSocket()->getNodeType(), ctx.getSocket()->getNodeID()};
}
void setRequestorID(std::pair<NodeType, NumNodeID> id)
{
std::tie(requestorNodeType, requestorNodeID) = id;
}
protected:
typedef MirroredMessageBase<Derived> BaseType;
MirroredMessageBase(unsigned short msgType)
: NetMessage(msgType)
{}
void serializePayload(Serializer& ser) const
{
ser % *(Derived*) this;
if (hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond))
ser
% serdes::as<uint8_t>(requestorNodeType)
% requestorNodeID;
}
bool deserializePayload(const char* buf, size_t bufLen)
{
Deserializer des(buf, bufLen);
des % *(Derived*) this;
if (hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond))
des
% serdes::as<uint8_t>(requestorNodeType)
% requestorNodeID;
return des.good();
}
private:
NumNodeID requestorNodeID;
NodeType requestorNodeType;
};

View File

@@ -0,0 +1,285 @@
/*
* Main purpose is to convert NetMessage defines into a string
*/
#pragma once
#include <common/net/message/NetMessageTypes.h>
#include <string>
inline std::string netMessageTypeToStr(int type)
{
/*
Cases generated with shell command:
awk '/NETMSGTYPE/ { printf("case %s: return \"%s (%i)\";\n", $2, substr($2, 12), $3); }' \
common/net/message/NetMessageTypes.h
*/
switch (type) {
case NETMSGTYPE_Invalid: return "Invalid (0)";
case NETMSGTYPE_RemoveNode: return "RemoveNode (1013)";
case NETMSGTYPE_RemoveNodeResp: return "RemoveNodeResp (1014)";
case NETMSGTYPE_GetNodes: return "GetNodes (1017)";
case NETMSGTYPE_GetNodesResp: return "GetNodesResp (1018)";
case NETMSGTYPE_HeartbeatRequest: return "HeartbeatRequest (1019)";
case NETMSGTYPE_Heartbeat: return "Heartbeat (1020)";
case NETMSGTYPE_GetNodeCapacityPools: return "GetNodeCapacityPools (1021)";
case NETMSGTYPE_GetNodeCapacityPoolsResp: return "GetNodeCapacityPoolsResp (1022)";
case NETMSGTYPE_MapTargets: return "MapTargets (1023)";
case NETMSGTYPE_MapTargetsResp: return "MapTargetsResp (1024)";
case NETMSGTYPE_GetTargetMappings: return "GetTargetMappings (1025)";
case NETMSGTYPE_GetTargetMappingsResp: return "GetTargetMappingsResp (1026)";
case NETMSGTYPE_UnmapTarget: return "UnmapTarget (1027)";
case NETMSGTYPE_UnmapTargetResp: return "UnmapTargetResp (1028)";
case NETMSGTYPE_GenericDebug: return "GenericDebug (1029)";
case NETMSGTYPE_GenericDebugResp: return "GenericDebugResp (1030)";
case NETMSGTYPE_GetClientStats: return "GetClientStats (1031)";
case NETMSGTYPE_GetClientStatsResp: return "GetClientStatsResp (1032)";
case NETMSGTYPE_RefreshCapacityPools: return "RefreshCapacityPools (1035)";
case NETMSGTYPE_StorageBenchControlMsg: return "StorageBenchControlMsg (1037)";
case NETMSGTYPE_StorageBenchControlMsgResp: return "StorageBenchControlMsgResp (1038)";
case NETMSGTYPE_RegisterNode: return "RegisterNode (1039)";
case NETMSGTYPE_RegisterNodeResp: return "RegisterNodeResp (1040)";
case NETMSGTYPE_RegisterTarget: return "RegisterTarget (1041)";
case NETMSGTYPE_RegisterTargetResp: return "RegisterTargetResp (1042)";
case NETMSGTYPE_SetMirrorBuddyGroup: return "SetMirrorBuddyGroup (1045)";
case NETMSGTYPE_SetMirrorBuddyGroupResp: return "SetMirrorBuddyGroupResp (1046)";
case NETMSGTYPE_GetMirrorBuddyGroups: return "GetMirrorBuddyGroups (1047)";
case NETMSGTYPE_GetMirrorBuddyGroupsResp: return "GetMirrorBuddyGroupsResp (1048)";
case NETMSGTYPE_GetTargetStates: return "GetTargetStates (1049)";
case NETMSGTYPE_GetTargetStatesResp: return "GetTargetStatesResp (1050)";
case NETMSGTYPE_RefreshTargetStates: return "RefreshTargetStates (1051)";
case NETMSGTYPE_GetStatesAndBuddyGroups: return "GetStatesAndBuddyGroups (1053)";
case NETMSGTYPE_GetStatesAndBuddyGroupsResp: return "GetStatesAndBuddyGroupsResp (1054)";
case NETMSGTYPE_SetTargetConsistencyStates: return "SetTargetConsistencyStates (1055)";
case NETMSGTYPE_SetTargetConsistencyStatesResp: return "SetTargetConsistencyStatesResp (1056)";
case NETMSGTYPE_ChangeTargetConsistencyStates: return "ChangeTargetConsistencyStates (1057)";
case NETMSGTYPE_ChangeTargetConsistencyStatesResp: return "ChangeTargetConsistencyStatesResp (1058)";
case NETMSGTYPE_PublishCapacities: return "PublishCapacities (1059)";
case NETMSGTYPE_RemoveBuddyGroup: return "RemoveBuddyGroup (1060)";
case NETMSGTYPE_RemoveBuddyGroupResp: return "RemoveBuddyGroupResp (1061)";
case NETMSGTYPE_GetTargetConsistencyStates: return "GetTargetConsistencyStates (1062)";
case NETMSGTYPE_GetTargetConsistencyStatesResp: return "GetTargetConsistencyStatesResp (1063)";
case NETMSGTYPE_AddStoragePool: return "AddStoragePool (1064)";
case NETMSGTYPE_AddStoragePoolResp: return "AddStoragePoolResp (1065)";
case NETMSGTYPE_GetStoragePools: return "GetStoragePools (1066)";
case NETMSGTYPE_GetStoragePoolsResp: return "GetStoragePoolsResp (1067)";
case NETMSGTYPE_ModifyStoragePool: return "ModifyStoragePool (1068)";
case NETMSGTYPE_ModifyStoragePoolResp: return "ModifyStoragePoolResp (1069)";
case NETMSGTYPE_RefreshStoragePools: return "RefreshStoragePools (1070)";
case NETMSGTYPE_RemoveStoragePool: return "RemoveStoragePool (1071)";
case NETMSGTYPE_RemoveStoragePoolResp: return "RemoveStoragePoolResp (1072)";
case NETMSGTYPE_MkDir: return "MkDir (2001)";
case NETMSGTYPE_MkDirResp: return "MkDirResp (2002)";
case NETMSGTYPE_RmDir: return "RmDir (2003)";
case NETMSGTYPE_RmDirResp: return "RmDirResp (2004)";
case NETMSGTYPE_MkFile: return "MkFile (2005)";
case NETMSGTYPE_MkFileResp: return "MkFileResp (2006)";
case NETMSGTYPE_UnlinkFile: return "UnlinkFile (2007)";
case NETMSGTYPE_UnlinkFileResp: return "UnlinkFileResp (2008)";
case NETMSGTYPE_UnlinkLocalFile: return "UnlinkLocalFile (2011)";
case NETMSGTYPE_UnlinkLocalFileResp: return "UnlinkLocalFileResp (2012)";
case NETMSGTYPE_Stat: return "Stat (2015)";
case NETMSGTYPE_StatResp: return "StatResp (2016)";
case NETMSGTYPE_GetChunkFileAttribs: return "GetChunkFileAttribs (2017)";
case NETMSGTYPE_GetChunkFileAttribsResp: return "GetChunkFileAttribsResp (2018)";
case NETMSGTYPE_TruncFile: return "TruncFile (2019)";
case NETMSGTYPE_TruncFileResp: return "TruncFileResp (2020)";
case NETMSGTYPE_TruncLocalFile: return "TruncLocalFile (2021)";
case NETMSGTYPE_TruncLocalFileResp: return "TruncLocalFileResp (2022)";
case NETMSGTYPE_Rename: return "Rename (2023)";
case NETMSGTYPE_RenameResp: return "RenameResp (2024)";
case NETMSGTYPE_SetAttr: return "SetAttr (2025)";
case NETMSGTYPE_SetAttrResp: return "SetAttrResp (2026)";
case NETMSGTYPE_ListDirFromOffset: return "ListDirFromOffset (2029)";
case NETMSGTYPE_ListDirFromOffsetResp: return "ListDirFromOffsetResp (2030)";
case NETMSGTYPE_StatStoragePath: return "StatStoragePath (2031)";
case NETMSGTYPE_StatStoragePathResp: return "StatStoragePathResp (2032)";
case NETMSGTYPE_SetLocalAttr: return "SetLocalAttr (2033)";
case NETMSGTYPE_SetLocalAttrResp: return "SetLocalAttrResp (2034)";
case NETMSGTYPE_FindOwner: return "FindOwner (2035)";
case NETMSGTYPE_FindOwnerResp: return "FindOwnerResp (2036)";
case NETMSGTYPE_MkLocalDir: return "MkLocalDir (2037)";
case NETMSGTYPE_MkLocalDirResp: return "MkLocalDirResp (2038)";
case NETMSGTYPE_RmLocalDir: return "RmLocalDir (2039)";
case NETMSGTYPE_RmLocalDirResp: return "RmLocalDirResp (2040)";
case NETMSGTYPE_MovingFileInsert: return "MovingFileInsert (2041)";
case NETMSGTYPE_MovingFileInsertResp: return "MovingFileInsertResp (2042)";
case NETMSGTYPE_MovingDirInsert: return "MovingDirInsert (2043)";
case NETMSGTYPE_MovingDirInsertResp: return "MovingDirInsertResp (2044)";
case NETMSGTYPE_GetEntryInfo: return "GetEntryInfo (2045)";
case NETMSGTYPE_GetEntryInfoResp: return "GetEntryInfoResp (2046)";
case NETMSGTYPE_SetDirPattern: return "SetDirPattern (2047)";
case NETMSGTYPE_SetDirPatternResp: return "SetDirPatternResp (2048)";
case NETMSGTYPE_GetHighResStats: return "GetHighResStats (2051)";
case NETMSGTYPE_GetHighResStatsResp: return "GetHighResStatsResp (2052)";
case NETMSGTYPE_MkFileWithPattern: return "MkFileWithPattern (2053)";
case NETMSGTYPE_MkFileWithPatternResp: return "MkFileWithPatternResp (2054)";
case NETMSGTYPE_RefreshEntryInfo: return "RefreshEntryInfo (2055)";
case NETMSGTYPE_RefreshEntryInfoResp: return "RefreshEntryInfoResp (2056)";
case NETMSGTYPE_RmDirEntry: return "RmDirEntry (2057)";
case NETMSGTYPE_RmDirEntryResp: return "RmDirEntryResp (2058)";
case NETMSGTYPE_LookupIntent: return "LookupIntent (2059)";
case NETMSGTYPE_LookupIntentResp: return "LookupIntentResp (2060)";
case NETMSGTYPE_FindLinkOwner: return "FindLinkOwner (2063)";
case NETMSGTYPE_FindLinkOwnerResp: return "FindLinkOwnerResp (2064)";
case NETMSGTYPE_MirrorMetadata: return "MirrorMetadata (2067)";
case NETMSGTYPE_MirrorMetadataResp: return "MirrorMetadataResp (2068)";
case NETMSGTYPE_SetMetadataMirroring: return "SetMetadataMirroring (2069)";
case NETMSGTYPE_SetMetadataMirroringResp: return "SetMetadataMirroringResp (2070)";
case NETMSGTYPE_Hardlink: return "Hardlink (2071)";
case NETMSGTYPE_HardlinkResp: return "HardlinkResp (2072)";
case NETMSGTYPE_SetQuota: return "SetQuota (2075)";
case NETMSGTYPE_SetQuotaResp: return "SetQuotaResp (2076)";
case NETMSGTYPE_SetExceededQuota: return "SetExceededQuota (2077)";
case NETMSGTYPE_SetExceededQuotaResp: return "SetExceededQuotaResp (2078)";
case NETMSGTYPE_RequestExceededQuota: return "RequestExceededQuota (2079)";
case NETMSGTYPE_RequestExceededQuotaResp: return "RequestExceededQuotaResp (2080)";
case NETMSGTYPE_UpdateDirParent: return "UpdateDirParent (2081)";
case NETMSGTYPE_UpdateDirParentResp: return "UpdateDirParentResp (2082)";
case NETMSGTYPE_ResyncLocalFile: return "ResyncLocalFile (2083)";
case NETMSGTYPE_ResyncLocalFileResp: return "ResyncLocalFileResp (2084)";
case NETMSGTYPE_StartStorageTargetResync: return "StartStorageTargetResync (2085)";
case NETMSGTYPE_StartStorageTargetResyncResp: return "StartStorageTargetResyncResp (2086)";
case NETMSGTYPE_StorageResyncStarted: return "StorageResyncStarted (2087)";
case NETMSGTYPE_StorageResyncStartedResp: return "StorageResyncStartedResp (2088)";
case NETMSGTYPE_ListChunkDirIncremental: return "ListChunkDirIncremental (2089)";
case NETMSGTYPE_ListChunkDirIncrementalResp: return "ListChunkDirIncrementalResp (2090)";
case NETMSGTYPE_RmChunkPaths: return "RmChunkPaths (2091)";
case NETMSGTYPE_RmChunkPathsResp: return "RmChunkPathsResp (2092)";
case NETMSGTYPE_GetStorageResyncStats: return "GetStorageResyncStats (2093)";
case NETMSGTYPE_GetStorageResyncStatsResp: return "GetStorageResyncStatsResp (2094)";
case NETMSGTYPE_SetLastBuddyCommOverride: return "SetLastBuddyCommOverride (2095)";
case NETMSGTYPE_SetLastBuddyCommOverrideResp: return "SetLastBuddyCommOverrideResp (2096)";
case NETMSGTYPE_GetQuotaInfo: return "GetQuotaInfo (2097)";
case NETMSGTYPE_GetQuotaInfoResp: return "GetQuotaInfoResp (2098)";
case NETMSGTYPE_SetStorageTargetInfo: return "SetStorageTargetInfo (2099)";
case NETMSGTYPE_SetStorageTargetInfoResp: return "SetStorageTargetInfoResp (2100)";
case NETMSGTYPE_ListXAttr: return "ListXAttr (2101)";
case NETMSGTYPE_ListXAttrResp: return "ListXAttrResp (2102)";
case NETMSGTYPE_GetXAttr: return "GetXAttr (2103)";
case NETMSGTYPE_GetXAttrResp: return "GetXAttrResp (2104)";
case NETMSGTYPE_RemoveXAttr: return "RemoveXAttr (2105)";
case NETMSGTYPE_RemoveXAttrResp: return "RemoveXAttrResp (2106)";
case NETMSGTYPE_SetXAttr: return "SetXAttr (2107)";
case NETMSGTYPE_SetXAttrResp: return "SetXAttrResp (2108)";
case NETMSGTYPE_GetDefaultQuota: return "GetDefaultQuota (2109)";
case NETMSGTYPE_GetDefaultQuotaResp: return "GetDefaultQuotaResp (2110)";
case NETMSGTYPE_SetDefaultQuota: return "SetDefaultQuota (2111)";
case NETMSGTYPE_SetDefaultQuotaResp: return "SetDefaultQuotaResp (2112)";
case NETMSGTYPE_ResyncSessionStore: return "ResyncSessionStore (2113)";
case NETMSGTYPE_ResyncSessionStoreResp: return "ResyncSessionStoreResp (2114)";
case NETMSGTYPE_ResyncRawInodes: return "ResyncRawInodes (2115)";
case NETMSGTYPE_ResyncRawInodesResp: return "ResyncRawInodesResp (2116)";
case NETMSGTYPE_GetMetaResyncStats: return "GetMetaResyncStats (2117)";
case NETMSGTYPE_GetMetaResyncStatsResp: return "GetMetaResyncStatsResp (2118)";
case NETMSGTYPE_MoveFileInode: return "MoveFileInode (2119)";
case NETMSGTYPE_MoveFileInodeResp: return "MoveFileInodeResp(2120)";
case NETMSGTYPE_UnlinkLocalFileInode: return "UnlinkLocalFileInode (2121)";
case NETMSGTYPE_UnlinkLocalFileInodeResp: return "UnlinkLocalFileInodeResp (2122)";
case NETMSGTYPE_SetFilePattern: return "SetFilePattern (2123)";
case NETMSGTYPE_SetFilePatternResp: return "SetFilePatternResp (2124)";
case NETMSGTYPE_CpChunkPaths: return "CpChunkPaths (2125)";
case NETMSGTYPE_CpChunkPathsResp: return "CpChunkPathsResp (2126)";
case NETMSGTYPE_ChunkBalance: return "StartChunkBalance (2127)";
case NETMSGTYPE_ChunkBalanceResp: return "StartChunkBalanceResp (2128)";
case NETMSGTYPE_StripePatternUpdate: return "StripePatternUpdate (2129)";
case NETMSGTYPE_StripePatternUpdateResp: return "StripePatternUpdateResp (2130)";
case NETMSGTYPE_SetFileState: return "SetFileState (2131)";
case NETMSGTYPE_SetFileStateResp: return "SetFileStateResp (2132)";
case NETMSGTYPE_OpenFile: return "OpenFile (3001)";
case NETMSGTYPE_OpenFileResp: return "OpenFileResp (3002)";
case NETMSGTYPE_CloseFile: return "CloseFile (3003)";
case NETMSGTYPE_CloseFileResp: return "CloseFileResp (3004)";
case NETMSGTYPE_OpenLocalFile: return "OpenLocalFile (3005)";
case NETMSGTYPE_OpenLocalFileResp: return "OpenLocalFileResp (3006)";
case NETMSGTYPE_CloseChunkFile: return "CloseChunkFile (3007)";
case NETMSGTYPE_CloseChunkFileResp: return "CloseChunkFileResp (3008)";
case NETMSGTYPE_WriteLocalFile: return "WriteLocalFile (3009)";
case NETMSGTYPE_WriteLocalFileResp: return "WriteLocalFileResp (3010)";
case NETMSGTYPE_FSyncLocalFile: return "FSyncLocalFile (3013)";
case NETMSGTYPE_FSyncLocalFileResp: return "FSyncLocalFileResp (3014)";
case NETMSGTYPE_ReadLocalFileV2: return "ReadLocalFileV2 (3019)";
case NETMSGTYPE_RefreshSession: return "RefreshSession (3021)";
case NETMSGTYPE_RefreshSessionResp: return "RefreshSessionResp (3022)";
case NETMSGTYPE_LockGranted: return "LockGranted (3023)";
case NETMSGTYPE_FLockEntry: return "FLockEntry (3025)";
case NETMSGTYPE_FLockEntryResp: return "FLockEntryResp (3026)";
case NETMSGTYPE_FLockRange: return "FLockRange (3027)";
case NETMSGTYPE_FLockRangeResp: return "FLockRangeResp (3028)";
case NETMSGTYPE_FLockAppend: return "FLockAppend (3029)";
case NETMSGTYPE_FLockAppendResp: return "FLockAppendResp (3030)";
case NETMSGTYPE_AckNotify: return "AckNotify (3031)";
case NETMSGTYPE_AckNotifyResp: return "AckNotifyResp (3032)";
case NETMSGTYPE_BumpFileVersion: return "BumpFileVersion (3033)";
case NETMSGTYPE_BumpFileVersionResp: return "BumpFileVersionResp (3034)";
case NETMSGTYPE_GetFileVersion: return "GetFileVersion (3035)";
case NETMSGTYPE_GetFileVersionResp: return "GetFileVersionResp (3036)";
#ifdef BEEGFS_NVFS
case NETMSGTYPE_WriteLocalFileRDMA: return "WriteLocalFileRDMA (3037)";
case NETMSGTYPE_WriteLocalFileRDMAResp: return "WriteLocalFileRDMAResp (3038)";
case NETMSGTYPE_ReadLocalFileRDMA: return "ReadLocalFileRDMA (3039)";
case NETMSGTYPE_ReadLocalFileRDMAResp: return "ReadLocalFileRDMAResp (3040)";
#endif /* BEEGFS_NVFS */
case NETMSGTYPE_SetChannelDirect: return "SetChannelDirect (4001)";
case NETMSGTYPE_Ack: return "Ack (4003)";
case NETMSGTYPE_Dummy: return "Dummy (4005)";
case NETMSGTYPE_AuthenticateChannel: return "AuthenticateChannel (4007)";
case NETMSGTYPE_GenericResponse: return "GenericResponse (4009)";
case NETMSGTYPE_PeerInfo: return "PeerInfo (4011)";
case NETMSGTYPE_GetNodesFromRootMetaNode: return "GetNodesFromRootMetaNode (6001)";
case NETMSGTYPE_SendNodesList: return "SendNodesList (6002)";
case NETMSGTYPE_RequestMetaData: return "RequestMetaData (6003)";
case NETMSGTYPE_RequestStorageData: return "RequestStorageData (6004)";
case NETMSGTYPE_RequestMetaDataResp: return "RequestMetaDataResp (6005)";
case NETMSGTYPE_RequestStorageDataResp: return "RequestStorageDataResp (6006)";
case NETMSGTYPE_RetrieveDirEntries: return "RetrieveDirEntries (7001)";
case NETMSGTYPE_RetrieveDirEntriesResp: return "RetrieveDirEntriesResp (7002)";
case NETMSGTYPE_RetrieveInodes: return "RetrieveInodes (7003)";
case NETMSGTYPE_RetrieveInodesResp: return "RetrieveInodesResp (7004)";
case NETMSGTYPE_RetrieveChunks: return "RetrieveChunks (7005)";
case NETMSGTYPE_RetrieveChunksResp: return "RetrieveChunksResp (7006)";
case NETMSGTYPE_RetrieveFsIDs: return "RetrieveFsIDs (7007)";
case NETMSGTYPE_RetrieveFsIDsResp: return "RetrieveFsIDsResp (7008)";
case NETMSGTYPE_DeleteDirEntries: return "DeleteDirEntries (7009)";
case NETMSGTYPE_DeleteDirEntriesResp: return "DeleteDirEntriesResp (7010)";
case NETMSGTYPE_CreateDefDirInodes: return "CreateDefDirInodes (7011)";
case NETMSGTYPE_CreateDefDirInodesResp: return "CreateDefDirInodesResp (7012)";
case NETMSGTYPE_FixInodeOwnersInDentry: return "FixInodeOwnersInDentry (7013)";
case NETMSGTYPE_FixInodeOwnersInDentryResp: return "FixInodeOwnersInDentryResp (7014)";
case NETMSGTYPE_FixInodeOwners: return "FixInodeOwners (7015)";
case NETMSGTYPE_FixInodeOwnersResp: return "FixInodeOwnersResp (7016)";
case NETMSGTYPE_LinkToLostAndFound: return "LinkToLostAndFound (7017)";
case NETMSGTYPE_LinkToLostAndFoundResp: return "LinkToLostAndFoundResp (7018)";
case NETMSGTYPE_DeleteChunks: return "DeleteChunks (7019)";
case NETMSGTYPE_DeleteChunksResp: return "DeleteChunksResp (7020)";
case NETMSGTYPE_CreateEmptyContDirs: return "CreateEmptyContDirs (7021)";
case NETMSGTYPE_CreateEmptyContDirsResp: return "CreateEmptyContDirsResp (7022)";
case NETMSGTYPE_UpdateFileAttribs: return "UpdateFileAttribs (7023)";
case NETMSGTYPE_UpdateFileAttribsResp: return "UpdateFileAttribsResp (7024)";
case NETMSGTYPE_UpdateDirAttribs: return "UpdateDirAttribs (7025)";
case NETMSGTYPE_UpdateDirAttribsResp: return "UpdateDirAttribsResp (7026)";
case NETMSGTYPE_RemoveInodes: return "RemoveInodes (7027)";
case NETMSGTYPE_RemoveInodesResp: return "RemoveInodesResp (7028)";
case NETMSGTYPE_RecreateFsIDs: return "RecreateFsIDs (7031)";
case NETMSGTYPE_RecreateFsIDsResp: return "RecreateFsIDsResp (7032)";
case NETMSGTYPE_RecreateDentries: return "RecreateDentries (7033)";
case NETMSGTYPE_RecreateDentriesResp: return "RecreateDentriesResp (7034)";
case NETMSGTYPE_FsckModificationEvent: return "FsckModificationEvent (7035)";
case NETMSGTYPE_FsckSetEventLogging: return "FsckSetEventLogging (7036)";
case NETMSGTYPE_FsckSetEventLoggingResp: return "FsckSetEventLoggingResp (7037)";
case NETMSGTYPE_FetchFsckChunkList: return "FetchFsckChunkList (7038)";
case NETMSGTYPE_FetchFsckChunkListResp: return "FetchFsckChunkListResp (7039)";
case NETMSGTYPE_AdjustChunkPermissions: return "AdjustChunkPermissions (7040)";
case NETMSGTYPE_AdjustChunkPermissionsResp: return "AdjustChunkPermissionsResp (7041)";
case NETMSGTYPE_MoveChunkFile: return "MoveChunkFile (7042)";
case NETMSGTYPE_MoveChunkFileResp: return "MoveChunkFileResp (7043)";
case NETMSGTYPE_CheckAndRepairDupInode: return "CheckAndRepairDupInode (7044)";
case NETMSGTYPE_CheckAndRepairDupInodeResp: return "CheckAndRepairDupInodeResp (7045)";
}
return "unknown (" + std::to_string(type) + ")";
}

View File

@@ -0,0 +1,282 @@
#pragma once
/* This file MUST be kept in sync with the corresponding client file!
* See fhgfs_client/source/closed/common/net/message/NetMessageTypes.h
*
* Also do not forget to add net NetMessages to 'class NetMsgStrMapping'
*/
// invalid messages
#define NETMSGTYPE_Invalid 0
// nodes messages
#define NETMSGTYPE_RemoveNode 1013
#define NETMSGTYPE_RemoveNodeResp 1014
#define NETMSGTYPE_GetNodes 1017
#define NETMSGTYPE_GetNodesResp 1018
#define NETMSGTYPE_HeartbeatRequest 1019
#define NETMSGTYPE_Heartbeat 1020
#define NETMSGTYPE_GetNodeCapacityPools 1021
#define NETMSGTYPE_GetNodeCapacityPoolsResp 1022
#define NETMSGTYPE_MapTargets 1023
#define NETMSGTYPE_MapTargetsResp 1024
#define NETMSGTYPE_GetTargetMappings 1025
#define NETMSGTYPE_GetTargetMappingsResp 1026
#define NETMSGTYPE_UnmapTarget 1027
#define NETMSGTYPE_UnmapTargetResp 1028
#define NETMSGTYPE_GenericDebug 1029
#define NETMSGTYPE_GenericDebugResp 1030
#define NETMSGTYPE_GetClientStats 1031
#define NETMSGTYPE_GetClientStatsResp 1032
#define NETMSGTYPE_RefreshCapacityPools 1035
#define NETMSGTYPE_StorageBenchControlMsg 1037
#define NETMSGTYPE_StorageBenchControlMsgResp 1038
#define NETMSGTYPE_RegisterNode 1039
#define NETMSGTYPE_RegisterNodeResp 1040
#define NETMSGTYPE_RegisterTarget 1041
#define NETMSGTYPE_RegisterTargetResp 1042
#define NETMSGTYPE_SetMirrorBuddyGroup 1045
#define NETMSGTYPE_SetMirrorBuddyGroupResp 1046
#define NETMSGTYPE_GetMirrorBuddyGroups 1047
#define NETMSGTYPE_GetMirrorBuddyGroupsResp 1048
#define NETMSGTYPE_GetTargetStates 1049
#define NETMSGTYPE_GetTargetStatesResp 1050
#define NETMSGTYPE_RefreshTargetStates 1051
#define NETMSGTYPE_GetStatesAndBuddyGroups 1053
#define NETMSGTYPE_GetStatesAndBuddyGroupsResp 1054
#define NETMSGTYPE_SetTargetConsistencyStates 1055
#define NETMSGTYPE_SetTargetConsistencyStatesResp 1056
#define NETMSGTYPE_ChangeTargetConsistencyStates 1057
#define NETMSGTYPE_ChangeTargetConsistencyStatesResp 1058
#define NETMSGTYPE_PublishCapacities 1059
#define NETMSGTYPE_RemoveBuddyGroup 1060
#define NETMSGTYPE_RemoveBuddyGroupResp 1061
#define NETMSGTYPE_GetTargetConsistencyStates 1062
#define NETMSGTYPE_GetTargetConsistencyStatesResp 1063
#define NETMSGTYPE_AddStoragePool 1064
#define NETMSGTYPE_AddStoragePoolResp 1065
#define NETMSGTYPE_GetStoragePools 1066
#define NETMSGTYPE_GetStoragePoolsResp 1067
#define NETMSGTYPE_ModifyStoragePool 1068
#define NETMSGTYPE_ModifyStoragePoolResp 1069
#define NETMSGTYPE_RefreshStoragePools 1070
#define NETMSGTYPE_RemoveStoragePool 1071
#define NETMSGTYPE_RemoveStoragePoolResp 1072
// storage messages
#define NETMSGTYPE_MkDir 2001
#define NETMSGTYPE_MkDirResp 2002
#define NETMSGTYPE_RmDir 2003
#define NETMSGTYPE_RmDirResp 2004
#define NETMSGTYPE_MkFile 2005
#define NETMSGTYPE_MkFileResp 2006
#define NETMSGTYPE_UnlinkFile 2007
#define NETMSGTYPE_UnlinkFileResp 2008
#define NETMSGTYPE_UnlinkLocalFile 2011
#define NETMSGTYPE_UnlinkLocalFileResp 2012
#define NETMSGTYPE_Stat 2015
#define NETMSGTYPE_StatResp 2016
#define NETMSGTYPE_GetChunkFileAttribs 2017
#define NETMSGTYPE_GetChunkFileAttribsResp 2018
#define NETMSGTYPE_TruncFile 2019
#define NETMSGTYPE_TruncFileResp 2020
#define NETMSGTYPE_TruncLocalFile 2021
#define NETMSGTYPE_TruncLocalFileResp 2022
#define NETMSGTYPE_Rename 2023
#define NETMSGTYPE_RenameResp 2024
#define NETMSGTYPE_SetAttr 2025
#define NETMSGTYPE_SetAttrResp 2026
#define NETMSGTYPE_ListDirFromOffset 2029
#define NETMSGTYPE_ListDirFromOffsetResp 2030
#define NETMSGTYPE_StatStoragePath 2031
#define NETMSGTYPE_StatStoragePathResp 2032
#define NETMSGTYPE_SetLocalAttr 2033
#define NETMSGTYPE_SetLocalAttrResp 2034
#define NETMSGTYPE_FindOwner 2035
#define NETMSGTYPE_FindOwnerResp 2036
#define NETMSGTYPE_MkLocalDir 2037
#define NETMSGTYPE_MkLocalDirResp 2038
#define NETMSGTYPE_RmLocalDir 2039
#define NETMSGTYPE_RmLocalDirResp 2040
#define NETMSGTYPE_MovingFileInsert 2041
#define NETMSGTYPE_MovingFileInsertResp 2042
#define NETMSGTYPE_MovingDirInsert 2043
#define NETMSGTYPE_MovingDirInsertResp 2044
#define NETMSGTYPE_GetEntryInfo 2045
#define NETMSGTYPE_GetEntryInfoResp 2046
#define NETMSGTYPE_SetDirPattern 2047
#define NETMSGTYPE_SetDirPatternResp 2048
#define NETMSGTYPE_GetHighResStats 2051
#define NETMSGTYPE_GetHighResStatsResp 2052
#define NETMSGTYPE_MkFileWithPattern 2053
#define NETMSGTYPE_MkFileWithPatternResp 2054
#define NETMSGTYPE_RefreshEntryInfo 2055
#define NETMSGTYPE_RefreshEntryInfoResp 2056
#define NETMSGTYPE_RmDirEntry 2057
#define NETMSGTYPE_RmDirEntryResp 2058
#define NETMSGTYPE_LookupIntent 2059
#define NETMSGTYPE_LookupIntentResp 2060
#define NETMSGTYPE_FindLinkOwner 2063
#define NETMSGTYPE_FindLinkOwnerResp 2064
#define NETMSGTYPE_MirrorMetadata 2067
#define NETMSGTYPE_MirrorMetadataResp 2068
#define NETMSGTYPE_SetMetadataMirroring 2069
#define NETMSGTYPE_SetMetadataMirroringResp 2070
#define NETMSGTYPE_Hardlink 2071
#define NETMSGTYPE_HardlinkResp 2072
#define NETMSGTYPE_SetQuota 2075
#define NETMSGTYPE_SetQuotaResp 2076
#define NETMSGTYPE_SetExceededQuota 2077
#define NETMSGTYPE_SetExceededQuotaResp 2078
#define NETMSGTYPE_RequestExceededQuota 2079
#define NETMSGTYPE_RequestExceededQuotaResp 2080
#define NETMSGTYPE_UpdateDirParent 2081
#define NETMSGTYPE_UpdateDirParentResp 2082
#define NETMSGTYPE_ResyncLocalFile 2083
#define NETMSGTYPE_ResyncLocalFileResp 2084
#define NETMSGTYPE_StartStorageTargetResync 2085
#define NETMSGTYPE_StartStorageTargetResyncResp 2086
#define NETMSGTYPE_StorageResyncStarted 2087
#define NETMSGTYPE_StorageResyncStartedResp 2088
#define NETMSGTYPE_ListChunkDirIncremental 2089
#define NETMSGTYPE_ListChunkDirIncrementalResp 2090
#define NETMSGTYPE_RmChunkPaths 2091
#define NETMSGTYPE_RmChunkPathsResp 2092
#define NETMSGTYPE_GetStorageResyncStats 2093
#define NETMSGTYPE_GetStorageResyncStatsResp 2094
#define NETMSGTYPE_SetLastBuddyCommOverride 2095
#define NETMSGTYPE_SetLastBuddyCommOverrideResp 2096
#define NETMSGTYPE_GetQuotaInfo 2097
#define NETMSGTYPE_GetQuotaInfoResp 2098
#define NETMSGTYPE_SetStorageTargetInfo 2099
#define NETMSGTYPE_SetStorageTargetInfoResp 2100
#define NETMSGTYPE_ListXAttr 2101
#define NETMSGTYPE_ListXAttrResp 2102
#define NETMSGTYPE_GetXAttr 2103
#define NETMSGTYPE_GetXAttrResp 2104
#define NETMSGTYPE_RemoveXAttr 2105
#define NETMSGTYPE_RemoveXAttrResp 2106
#define NETMSGTYPE_SetXAttr 2107
#define NETMSGTYPE_SetXAttrResp 2108
#define NETMSGTYPE_GetDefaultQuota 2109
#define NETMSGTYPE_GetDefaultQuotaResp 2110
#define NETMSGTYPE_SetDefaultQuota 2111
#define NETMSGTYPE_SetDefaultQuotaResp 2112
#define NETMSGTYPE_ResyncSessionStore 2113
#define NETMSGTYPE_ResyncSessionStoreResp 2114
#define NETMSGTYPE_ResyncRawInodes 2115
#define NETMSGTYPE_ResyncRawInodesResp 2116
#define NETMSGTYPE_GetMetaResyncStats 2117
#define NETMSGTYPE_GetMetaResyncStatsResp 2118
#define NETMSGTYPE_MoveFileInode 2119
#define NETMSGTYPE_MoveFileInodeResp 2120
#define NETMSGTYPE_UnlinkLocalFileInode 2121
#define NETMSGTYPE_UnlinkLocalFileInodeResp 2122
#define NETMSGTYPE_SetFilePattern 2123
#define NETMSGTYPE_SetFilePatternResp 2124
#define NETMSGTYPE_CpChunkPaths 2125
#define NETMSGTYPE_CpChunkPathsResp 2126
#define NETMSGTYPE_ChunkBalance 2127
#define NETMSGTYPE_ChunkBalanceResp 2128
#define NETMSGTYPE_StripePatternUpdate 2129
#define NETMSGTYPE_StripePatternUpdateResp 2130
#define NETMSGTYPE_SetFileState 2131
#define NETMSGTYPE_SetFileStateResp 2132
// session messages
#define NETMSGTYPE_OpenFile 3001
#define NETMSGTYPE_OpenFileResp 3002
#define NETMSGTYPE_CloseFile 3003
#define NETMSGTYPE_CloseFileResp 3004
#define NETMSGTYPE_OpenLocalFile 3005
#define NETMSGTYPE_OpenLocalFileResp 3006
#define NETMSGTYPE_CloseChunkFile 3007
#define NETMSGTYPE_CloseChunkFileResp 3008
#define NETMSGTYPE_WriteLocalFile 3009
#define NETMSGTYPE_WriteLocalFileResp 3010
#define NETMSGTYPE_FSyncLocalFile 3013
#define NETMSGTYPE_FSyncLocalFileResp 3014
#define NETMSGTYPE_ReadLocalFileV2 3019
#define NETMSGTYPE_RefreshSession 3021
#define NETMSGTYPE_RefreshSessionResp 3022
#define NETMSGTYPE_LockGranted 3023
#define NETMSGTYPE_FLockEntry 3025
#define NETMSGTYPE_FLockEntryResp 3026
#define NETMSGTYPE_FLockRange 3027
#define NETMSGTYPE_FLockRangeResp 3028
#define NETMSGTYPE_FLockAppend 3029
#define NETMSGTYPE_FLockAppendResp 3030
#define NETMSGTYPE_AckNotify 3031
#define NETMSGTYPE_AckNotifyResp 3032
#define NETMSGTYPE_BumpFileVersion 3033
#define NETMSGTYPE_BumpFileVersionResp 3034
#define NETMSGTYPE_GetFileVersion 3035
#define NETMSGTYPE_GetFileVersionResp 3036
#ifdef BEEGFS_NVFS
#define NETMSGTYPE_WriteLocalFileRDMA 3037
#define NETMSGTYPE_WriteLocalFileRDMAResp 3038
#define NETMSGTYPE_ReadLocalFileRDMA 3039
#define NETMSGTYPE_ReadLocalFileRDMAResp 3040
#endif /* BEEGFS_NVFS */
// control messages
#define NETMSGTYPE_SetChannelDirect 4001
#define NETMSGTYPE_Ack 4003
#define NETMSGTYPE_Dummy 4005
#define NETMSGTYPE_AuthenticateChannel 4007
#define NETMSGTYPE_GenericResponse 4009
#define NETMSGTYPE_PeerInfo 4011
// mon messages
#define NETMSGTYPE_GetNodesFromRootMetaNode 6001
#define NETMSGTYPE_SendNodesList 6002
#define NETMSGTYPE_RequestMetaData 6003
#define NETMSGTYPE_RequestStorageData 6004
#define NETMSGTYPE_RequestMetaDataResp 6005
#define NETMSGTYPE_RequestStorageDataResp 6006
// fsck messages
#define NETMSGTYPE_RetrieveDirEntries 7001
#define NETMSGTYPE_RetrieveDirEntriesResp 7002
#define NETMSGTYPE_RetrieveInodes 7003
#define NETMSGTYPE_RetrieveInodesResp 7004
#define NETMSGTYPE_RetrieveChunks 7005
#define NETMSGTYPE_RetrieveChunksResp 7006
#define NETMSGTYPE_RetrieveFsIDs 7007
#define NETMSGTYPE_RetrieveFsIDsResp 7008
#define NETMSGTYPE_DeleteDirEntries 7009
#define NETMSGTYPE_DeleteDirEntriesResp 7010
#define NETMSGTYPE_CreateDefDirInodes 7011
#define NETMSGTYPE_CreateDefDirInodesResp 7012
#define NETMSGTYPE_FixInodeOwnersInDentry 7013
#define NETMSGTYPE_FixInodeOwnersInDentryResp 7014
#define NETMSGTYPE_FixInodeOwners 7015
#define NETMSGTYPE_FixInodeOwnersResp 7016
#define NETMSGTYPE_LinkToLostAndFound 7017
#define NETMSGTYPE_LinkToLostAndFoundResp 7018
#define NETMSGTYPE_DeleteChunks 7019
#define NETMSGTYPE_DeleteChunksResp 7020
#define NETMSGTYPE_CreateEmptyContDirs 7021
#define NETMSGTYPE_CreateEmptyContDirsResp 7022
#define NETMSGTYPE_UpdateFileAttribs 7023
#define NETMSGTYPE_UpdateFileAttribsResp 7024
#define NETMSGTYPE_UpdateDirAttribs 7025
#define NETMSGTYPE_UpdateDirAttribsResp 7026
#define NETMSGTYPE_RemoveInodes 7027
#define NETMSGTYPE_RemoveInodesResp 7028
#define NETMSGTYPE_RecreateFsIDs 7031
#define NETMSGTYPE_RecreateFsIDsResp 7032
#define NETMSGTYPE_RecreateDentries 7033
#define NETMSGTYPE_RecreateDentriesResp 7034
#define NETMSGTYPE_FsckModificationEvent 7035
#define NETMSGTYPE_FsckSetEventLogging 7036
#define NETMSGTYPE_FsckSetEventLoggingResp 7037
#define NETMSGTYPE_FetchFsckChunkList 7038
#define NETMSGTYPE_FetchFsckChunkListResp 7039
#define NETMSGTYPE_AdjustChunkPermissions 7040
#define NETMSGTYPE_AdjustChunkPermissionsResp 7041
#define NETMSGTYPE_MoveChunkFile 7042
#define NETMSGTYPE_MoveChunkFileResp 7043
#define NETMSGTYPE_CheckAndRepairDupInode 7044
#define NETMSGTYPE_CheckAndRepairDupInodeResp 7045

View File

@@ -0,0 +1,30 @@
#pragma once
#include "NetMessage.h"
class SimpleInt64Msg : public NetMessageSerdes<SimpleInt64Msg>
{
protected:
SimpleInt64Msg(unsigned short msgType, int64_t value) : BaseType(msgType)
{
this->value = value;
}
SimpleInt64Msg(unsigned short msgType) : BaseType(msgType)
{
}
public:
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx % obj->value;
}
private:
int64_t value;
public:
int64_t getValue() const { return value; }
};

View File

@@ -0,0 +1,30 @@
#pragma once
#include "NetMessage.h"
class SimpleIntMsg : public NetMessageSerdes<SimpleIntMsg>
{
protected:
SimpleIntMsg(unsigned short msgType, int value) : BaseType(msgType)
{
this->value = value;
}
SimpleIntMsg(unsigned short msgType) : BaseType(msgType)
{
}
public:
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx % obj->value;
}
private:
int32_t value;
public:
int getValue() const { return value; }
};

View File

@@ -0,0 +1,47 @@
#pragma once
#include "NetMessage.h"
/**
* Simple message containing an integer value and a string (e.g. int error code and human-readable
* explantion with more details as string).
*/
class SimpleIntStringMsg : public NetMessageSerdes<SimpleIntStringMsg>
{
protected:
/**
* @param strValue just a reference
*/
SimpleIntStringMsg(unsigned short msgType, int intValue, std::string strValue) :
BaseType(msgType),
intValue(intValue), strValue(std::move(strValue))
{ }
/**
* For deserialization only!
*/
SimpleIntStringMsg(unsigned short msgType) : BaseType(msgType)
{
}
public:
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% obj->intValue
% obj->strValue;
}
private:
int32_t intValue;
std::string strValue;
public:
int getIntValue() const { return intValue; }
const std::string& getStrValue() const { return strValue; }
};

View File

@@ -0,0 +1,29 @@
#pragma once
#include "NetMessage.h"
/**
* Simple messages are defined by the header (resp. the msgType) only and
* require no additional data
*/
class SimpleMsg : public NetMessage
{
public:
SimpleMsg(unsigned short msgType) : NetMessage(msgType)
{
}
protected:
virtual void serializePayload(Serializer& ser) const
{
// nothing to be done here for simple messages
}
virtual bool deserializePayload(const char* buf, size_t bufLen)
{
// nothing to be done here for simple messages
return true;
}
};

View File

@@ -0,0 +1,47 @@
#pragma once
#include "NetMessage.h"
class SimpleStringMsg : public NetMessageSerdes<SimpleStringMsg>
{
protected:
/**
* @param value just a reference
*/
SimpleStringMsg(unsigned short msgType, const char* value) : BaseType(msgType)
{
this->value = value;
this->valueLen = strlen(value);
}
/**
* @param value just a reference
*/
SimpleStringMsg(unsigned short msgType, const std::string& value) : BaseType(msgType)
{
this->value = value.c_str();
this->valueLen = value.length();
}
/**
* For deserialization only!
*/
SimpleStringMsg(unsigned short msgType) : BaseType(msgType)
{
}
public:
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx % serdes::rawString(obj->value, obj->valueLen);
}
private:
const char* value;
unsigned valueLen;
public:
const char* getValue() const { return value; }
};

View File

@@ -0,0 +1,33 @@
#pragma once
#include "NetMessage.h"
class SimpleUInt16Msg : public NetMessageSerdes<SimpleUInt16Msg>
{
protected:
SimpleUInt16Msg(unsigned short msgType, uint16_t value) : BaseType(msgType)
{
this->value = value;
}
/**
* Constructor for deserialization only!
*/
SimpleUInt16Msg(unsigned short msgType) : BaseType(msgType)
{
}
public:
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx % obj->value;
}
private:
uint16_t value;
public:
uint16_t getValue() const { return value; }
};

View File

@@ -0,0 +1,18 @@
#pragma once
#include <common/net/message/SimpleStringMsg.h>
// see class AcknowledgeableMsg (fhgfs_common) for a short description
class AckMsg : public SimpleStringMsg
{
public:
AckMsg(const char* ackID) : SimpleStringMsg(NETMSGTYPE_Ack, ackID)
{
}
AckMsg() : SimpleStringMsg(NETMSGTYPE_Ack)
{
}
};

View File

@@ -0,0 +1,33 @@
#pragma once
#include <common/net/message/SimpleInt64Msg.h>
class AuthenticateChannelMsg : public SimpleInt64Msg
{
public:
AuthenticateChannelMsg(uint64_t authHash) :
SimpleInt64Msg(NETMSGTYPE_AuthenticateChannel, authHash)
{
}
/**
* For deserialization only
*/
AuthenticateChannelMsg() : SimpleInt64Msg(NETMSGTYPE_AuthenticateChannel)
{
}
private:
public:
// getters & setters
uint64_t getAuthHash()
{
return getValue();
}
};

View File

@@ -0,0 +1,29 @@
#include <common/app/config/ICommonConfig.h>
#include <common/app/log/LogContext.h>
#include <common/app/AbstractApp.h>
#include "AuthenticateChannelMsgEx.h"
bool AuthenticateChannelMsgEx::processIncoming(ResponseContext& ctx)
{
const char* logContext = "AuthenticateChannel incoming";
AbstractApp* app = PThread::getCurrentThreadApp();
auto cfg = app->getCommonConfig();
uint64_t authHash = cfg->getConnAuthHash();
if(authHash && (authHash != getAuthHash() ) )
{ // authentication failed
LogContext(logContext).log(Log_WARNING,
"Peer sent wrong authentication: " + ctx.peerName() );
}
else
{
ctx.getSocket()->setIsAuthenticated();
}
// note: this message does not require a response
return true;
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include <common/net/message/control/AuthenticateChannelMsg.h>
class AuthenticateChannelMsgEx : public AuthenticateChannelMsg
{
public:
virtual bool processIncoming(ResponseContext& ctx);
};

View File

@@ -0,0 +1,13 @@
#pragma once
#include <common/net/message/SimpleMsg.h>
class DummyMsg : public SimpleMsg
{
public:
DummyMsg() : SimpleMsg(NETMSGTYPE_Dummy)
{
}
};

View File

@@ -0,0 +1,67 @@
#pragma once
#include <common/net/message/SimpleIntStringMsg.h>
/**
* Note: Remember to keep this in sync with client.
*/
enum GenericRespMsgCode
{
GenericRespMsgCode_TRYAGAIN = 0, /* requestor shall try again in a few seconds */
GenericRespMsgCode_INDIRECTCOMMERR = 1, /* indirect communication error and requestor should
try again (e.g. msg forwarding to other server failed) */
GenericRespMsgCode_NEWSEQNOBASE = 2, /* client has restarted its seq# sequence. server provides
the new starting seq#. */
};
/**
* A generic response that can be sent as a reply to any message. This special control message will
* be handled internally by the requestors MessageTk::requestResponse...() method.
*
* This is intended to submit internal information (like asking for a communication retry) to the
* requestors MessageTk through the control code. So the MessageTk caller on the requestor side
* will never actually see this GenericResponseMsg.
*
* The message string is intended to provide additional human-readable information like why this
* control code was submitted instead of the actually expected response msg.
*/
class GenericResponseMsg : public SimpleIntStringMsg
{
public:
/**
* @param controlCode GenericRespMsgCode_...
* @param logStr human-readable explanation or background information.
*/
GenericResponseMsg(GenericRespMsgCode controlCode, std::string logStr) :
SimpleIntStringMsg(NETMSGTYPE_GenericResponse, controlCode, std::move(logStr))
{
}
/**
* For deserialization only.
*/
GenericResponseMsg() : SimpleIntStringMsg(NETMSGTYPE_GenericResponse)
{
}
private:
public:
// getters & setters
GenericRespMsgCode getControlCode() const
{
return (GenericRespMsgCode)getIntValue();
}
const std::string& getLogStr() const
{
return getStrValue();
}
};

View File

@@ -0,0 +1,33 @@
#pragma once
#include <common/net/message/NetMessage.h>
#include <common/nodes/Node.h>
class PeerInfoMsg : public NetMessageSerdes<PeerInfoMsg>
{
public:
PeerInfoMsg(NodeType type, NumNodeID id)
: BaseType(NETMSGTYPE_PeerInfo), type(type), id(id)
{
}
/**
* For deserialization only
*/
PeerInfoMsg() : BaseType(NETMSGTYPE_PeerInfo)
{
}
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% serdes::as<uint32_t>(obj->type)
% obj->id;
}
protected:
NodeType type;
NumNodeID id;
};

View File

@@ -0,0 +1,16 @@
#include <common/app/config/ICommonConfig.h>
#include <common/app/log/LogContext.h>
#include <common/app/AbstractApp.h>
#include "PeerInfoMsgEx.h"
bool PeerInfoMsgEx::processIncoming(ResponseContext& ctx)
{
ctx.getSocket()->setNodeType(type);
ctx.getSocket()->setNodeID(id);
// note: this message does not require a response
return true;
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include <common/net/message/control/PeerInfoMsg.h>
class PeerInfoMsgEx : public PeerInfoMsg
{
public:
virtual bool processIncoming(ResponseContext& ctx);
};

View File

@@ -0,0 +1,19 @@
#pragma once
#include <common/net/message/SimpleIntMsg.h>
class SetChannelDirectMsg : public SimpleIntMsg
{
public:
SetChannelDirectMsg(int isDirect) : SimpleIntMsg(NETMSGTYPE_SetChannelDirect, isDirect)
{
}
/**
* For deserialization only
*/
SetChannelDirectMsg() : SimpleIntMsg(NETMSGTYPE_SetChannelDirect)
{
}
};

View File

@@ -0,0 +1,75 @@
#pragma once
#include <common/net/message/NetMessage.h>
class AdjustChunkPermissionsMsg : public NetMessageSerdes<AdjustChunkPermissionsMsg>
{
public:
AdjustChunkPermissionsMsg(unsigned hashDirNum, std::string& currentContDirID,
unsigned maxEntries, int64_t lastHashDirOffset, int64_t lastContDirOffset,
bool isBuddyMirrored) :
BaseType(NETMSGTYPE_AdjustChunkPermissions)
{
this->hashDirNum = hashDirNum;
this->currentContDirID = currentContDirID.c_str();
this->currentContDirIDLen = currentContDirID.length();
this->maxEntries = maxEntries;
this->lastHashDirOffset = lastHashDirOffset;
this->lastContDirOffset = lastContDirOffset;
this->isBuddyMirrored = isBuddyMirrored;
}
AdjustChunkPermissionsMsg() : BaseType(NETMSGTYPE_AdjustChunkPermissions)
{
}
private:
uint32_t hashDirNum;
const char *currentContDirID;
uint32_t currentContDirIDLen;
uint32_t maxEntries;
int64_t lastHashDirOffset;
int64_t lastContDirOffset;
bool isBuddyMirrored;
public:
std::string getCurrentContDirID() const
{
return currentContDirID;
}
unsigned getHashDirNum() const
{
return hashDirNum;
}
int64_t getLastContDirOffset() const
{
return lastContDirOffset;
}
int64_t getLastHashDirOffset() const
{
return lastHashDirOffset;
}
unsigned getMaxEntries() const
{
return maxEntries;
}
bool getIsBuddyMirrored() const { return isBuddyMirrored; }
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% serdes::rawString(obj->currentContDirID, obj->currentContDirIDLen, 4)
% obj->hashDirNum
% obj->maxEntries
% obj->lastHashDirOffset
% obj->lastContDirOffset
% obj->isBuddyMirrored;
}
};

View File

@@ -0,0 +1,71 @@
#pragma once
#include <common/net/message/NetMessage.h>
class AdjustChunkPermissionsRespMsg : public NetMessageSerdes<AdjustChunkPermissionsRespMsg>
{
public:
AdjustChunkPermissionsRespMsg(unsigned count, std::string& currentContDirID,
int64_t newHashDirOffset, int64_t newContDirOffset, unsigned errorCount) :
BaseType(NETMSGTYPE_AdjustChunkPermissionsResp)
{
this->count = count;
this->currentContDirID = currentContDirID.c_str();
this->currentContDirIDLen = currentContDirID.length();
this->newHashDirOffset = newHashDirOffset;
this->newContDirOffset = newContDirOffset;
this->errorCount = errorCount;
}
AdjustChunkPermissionsRespMsg() : BaseType(NETMSGTYPE_AdjustChunkPermissionsResp)
{
}
private:
const char* currentContDirID;
unsigned currentContDirIDLen;
uint32_t count;
int64_t newHashDirOffset;
int64_t newContDirOffset;
uint32_t errorCount;
public:
// getters & setters
unsigned getCount() const
{
return count;
}
int64_t getNewHashDirOffset() const
{
return newHashDirOffset;
}
int64_t getNewContDirOffset() const
{
return newContDirOffset;
}
std::string getCurrentContDirID() const
{
return currentContDirID;
}
bool getErrorCount() const
{
return errorCount;
}
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx
% serdes::rawString(obj->currentContDirID, obj->currentContDirIDLen, 4)
% obj->count
% obj->newHashDirOffset
% obj->newContDirOffset
% obj->errorCount;
}
};

View File

@@ -0,0 +1,41 @@
#pragma once
#include <common/net/message/NetMessage.h>
#include <common/fsck/FsckDuplicateInodeInfo.h>
class CheckAndRepairDupInodeMsg : public NetMessageSerdes<CheckAndRepairDupInodeMsg>
{
public:
CheckAndRepairDupInodeMsg(FsckDuplicateInodeInfoVector* items):
BaseType(NETMSGTYPE_CheckAndRepairDupInode), dupInodes(items)
{
}
CheckAndRepairDupInodeMsg() : BaseType(NETMSGTYPE_CheckAndRepairDupInode)
{
}
protected:
// not owned by this object
FsckDuplicateInodeInfoVector* dupInodes;
// for deserialization
struct
{
FsckDuplicateInodeInfoVector dupInodes;
} parsed;
public:
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx % serdes::backedPtr(obj->dupInodes, obj->parsed.dupInodes);
}
FsckDuplicateInodeInfoVector& getDuplicateInodes()
{
return *dupInodes;
}
};

View File

@@ -0,0 +1,31 @@
#pragma once
#include <common/net/message/NetMessage.h>
#include <common/storage/StorageDefinitions.h>
#include <common/toolkit/ListTk.h>
class CheckAndRepairDupInodeRespMsg : public NetMessageSerdes<CheckAndRepairDupInodeRespMsg>
{
public:
CheckAndRepairDupInodeRespMsg(StringList failedEntryIDList) :
BaseType(NETMSGTYPE_CheckAndRepairDupInodeResp), failedEntryIDList(std::move(failedEntryIDList))
{
}
CheckAndRepairDupInodeRespMsg() : BaseType(NETMSGTYPE_CheckAndRepairDupInodeResp)
{
}
private:
StringList failedEntryIDList;
public:
StringList releaseFailedEntryIDList() { return failedEntryIDList; }
template<typename This, typename Ctx>
static void serialize(This obj, Ctx& ctx)
{
ctx % obj->failedEntryIDList;
}
};

Some files were not shown because too many files have changed in this diff Show More