diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index cee502506c..3cc93c136d 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -104,6 +104,12 @@ jobs: # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest --build-config ${{ matrix.build_type }} + - uses: actions/upload-artifact@v4 + if: (success() || failure()) && matrix.os == 'ubuntu-22.04' + with: + name: test-results + path: "${{ steps.strings.outputs.build-output-dir }}/**/reports/junit-*.xml" + - name: Package run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} --target package diff --git a/.github/workflows/test-report.yml b/.github/workflows/test-report.yml new file mode 100644 index 0000000000..70d2946d09 --- /dev/null +++ b/.github/workflows/test-report.yml @@ -0,0 +1,20 @@ +name: "Test Report" +on: + workflow_run: + workflows: ["CMake on multiple platforms"] + types: + - completed +permissions: + contents: read + actions: read + checks: write +jobs: + report: + runs-on: ubuntu-latest + steps: + - uses: dorny/test-reporter@v1 + with: + artifact: test-results + name: GTests + path: '**/*.xml' + reporter: java-junit \ No newline at end of file diff --git a/.gitignore b/.gitignore index dfc254b44a..dacdb0c16c 100644 --- a/.gitignore +++ b/.gitignore @@ -160,11 +160,11 @@ snowballs/build/* ryzom/build/* build/* build-2010/* -build/* install/* build_* install_* nel/tools/build_gamedata/configuration/buildsite.py +cmake-build-* # Linux nel compile nel/build/nel-config diff --git a/CMakeLists.txt b/CMakeLists.txt index b1fd19305b..5adbad19bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -323,6 +323,17 @@ ENDIF() IF(WITH_NEL) IF(WITH_NEL_TESTS) FIND_PACKAGE(CppTest) + + ENABLE_TESTING() + INCLUDE(FetchContent) + FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/a7f443b80b105f940225332ed3c31f2790092f47.zip + EXCLUDE_FROM_ALL + ) + SET(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(googletest) + INCLUDE(GoogleTest) ENDIF() IF(HUNTER_ENABLED) diff --git a/nel/src/net/service.cpp b/nel/src/net/service.cpp index 925075f08f..4131da3170 100644 --- a/nel/src/net/service.cpp +++ b/nel/src/net/service.cpp @@ -375,6 +375,9 @@ IService::IService() : IService::~IService() { + // Singleton + _Instance = nullptr; + // unregister the singleton INelContext::getInstance().releaseSingletonPointer("IService", this); } diff --git a/nelns/naming_service/CMakeLists.txt b/nelns/naming_service/CMakeLists.txt index 596bb5b48f..29e81c7487 100644 --- a/nelns/naming_service/CMakeLists.txt +++ b/nelns/naming_service/CMakeLists.txt @@ -1,13 +1,31 @@ -FILE(GLOB SRC *.cpp *.h) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -ADD_EXECUTABLE(naming_service WIN32 ${SRC}) +add_library(nelns_naming_service STATIC + nelns/naming_service/functions.cpp + nelns/naming_service/naming_service.cpp + nelns/naming_service/service_instance_manager.cpp + nelns/naming_service/variables.cpp +) +add_library(nelns::ns ALIAS nelns_naming_service) +target_link_libraries(nelns_naming_service + nelmisc + nelnet +) + +ADD_EXECUTABLE(naming_service WIN32 + naming_service.cpp +) TARGET_LINK_LIBRARIES(naming_service - nelmisc - nelnet) + nelns::ns +) NL_DEFAULT_PROPS(naming_service "NeLNS, Services: Naming Service") NL_ADD_RUNTIME_FLAGS(naming_service) INSTALL(TARGETS naming_service RUNTIME DESTINATION sbin COMPONENT ns) INSTALL(FILES naming_service.cfg common.cfg DESTINATION ${NL_ETC_PREFIX}/nelns COMPONENT ns) + +IF (WITH_NEL_TESTS) + ADD_SUBDIRECTORY(tests) +ENDIF () diff --git a/nelns/naming_service/naming_service.cpp b/nelns/naming_service/naming_service.cpp index b10856088d..4787c958c5 100644 --- a/nelns/naming_service/naming_service.cpp +++ b/nelns/naming_service/naming_service.cpp @@ -34,19 +34,23 @@ // Includes // -#include "nel/misc/types_nl.h" +#include #include #include -#include "nel/misc/debug.h" -#include "nel/misc/command.h" -#include "nel/misc/variable.h" -#include "nel/misc/displayer.h" +#include +#include +#include +#include -#include "nel/net/callback_server.h" -#include "nel/net/service.h" -#include "nel/net/module_manager.h" +#include + +#include +#include +#include +#include +#include // // Namespaces @@ -70,995 +74,6 @@ NLMISC_COMMAND(test, "none", "none") } -// -// Structures -// - -struct CServiceEntry -{ - CServiceEntry (TSockId sock, const vector &a, const string &n, TServiceId s) : SockId(sock), Addr(a), Name(n), SId (s), WaitingUnregistration(false) { } - - TSockId SockId; // the connection between the service and the naming service - vector Addr; // address to send to the service who wants to lookup this service - // it s possible to have more than one addr, anyway, the naming service - // will send good address depending of the sub net address of the service - string Name; // name of the service - TServiceId SId; // id of the service - - bool WaitingUnregistration; // true if this service is in unregistration process (wait other service ACK) - TTime WaitingUnregistrationTime; // time of the beginning of the inregistration process - list WaitingUnregistrationServices; // list of service that we wait the answer -}; - - - -// Helper that emulates layer5's send() -//void sendToService( uint16 sid, CMessage& msgout ); - -// Helper that emulate layer5's getServiceName() -string getServiceName( TServiceId sid ); - -// Helper that returns the first address of a service -CInetAddress getHostAddress( TServiceId sid ); - -// Asks a service to stop and tell every one -void doUnregisterService (TServiceId sid); - - -/** - * Manager for services instances - * (Moved from the TICKS to the NS) - * Implementable with layer 5, here implemented in NS (layer 3) - * \author Olivier Cado - * \author Nevrax France - * \date 2003 - */ -class CServiceInstanceManager -{ -public: - - /// Constructor - CServiceInstanceManager(); - - /** Add the name of a service which must not be duplicated - * If uniqueOnShard is true, only one service is allowed. - * If uniqueOnShard is false, one service is allowed by physical machine. - */ - void addUniqueService( const std::string& serviceName, bool uniqueOnShard ) - { - _UniqueServices.insert( std::make_pair( serviceName, uniqueOnShard ) ); - } - - /// Check if a service is allowed to start (if so, add it) - bool queryStartService( const std::string& serviceName, TServiceId serviceId, const std::vector &addr, string& reason ); - - /// Release a service instance - void releaseService( NLNET::TServiceId serviceId ); - - /// Display information - void displayInfo( NLMISC::CLog *log = NLMISC::InfoLog ) const; - - /// Make all controlled services quit - void killAllServices(); - -private: - - /// List of restricted services - std::map< std::string, bool > _UniqueServices; - - /// List of granted (online) services - std::set< TServiceId > _OnlineServices; -}; - - -CServiceInstanceManager *SIMInstance = NULL; - - -/* - * Constructor - */ -CServiceInstanceManager::CServiceInstanceManager() -{ - nlassert( ! SIMInstance ); - SIMInstance = this; - - // Note: addCallbackArray() done in CRangeMirrorManager::init() -} - - -/* - * Check if a service is allowed to start. Answer with a GSTS (Grant Start Service) message - */ -bool CServiceInstanceManager::queryStartService( const std::string& serviceName, TServiceId serviceId, const vector &addr, string& reason ) -{ - bool grantStarting = true; - std::map< std::string, bool >::iterator ius = _UniqueServices.find( serviceName ); - if ( ius != _UniqueServices.end() ) - { - // Service is restricted - set< TServiceId >::iterator ios; - bool uniqueOnShard = (*ius).second; - for ( ios=_OnlineServices.begin(); ios!=_OnlineServices.end(); ++ios ) - { - string name = getServiceName( *ios ); - if ( name == serviceName ) - { - if ( uniqueOnShard ) - { - // Only one service by shard is allowed => deny - grantStarting = false; - reason = toString( "Service %s already found as %hu, must be unique on shard", serviceName.c_str(), ios->get() ); - nlinfo( reason.c_str() ); - break; - } - else - { - // Only one service by physical machine is allowed - - // Implementation for layer5 - //TSockId hostid1, hostid2; - /*CCallbackNetBase *cnb1 = CUnifiedNetwork::getInstance()->getNetBase( serviceId, hostid1 ); - CCallbackNetBase *cnb2 = CUnifiedNetwork::getInstance()->getNetBase( *ios, hostid2 ); - if ( cnb1->hostAddress( hostid1 ).internalIPAddress() == cnb2->hostAddress( hostid2 ).internalIPAddress() )*/ - - // Implementation for NS - if ( addr[0].getAddress() == getHostAddress( *ios ).getAddress() ) - { - grantStarting = false; - reason = toString( "Service %s already found as %hu on same machine", serviceName.c_str(), ios->get() ); - nlinfo( reason.c_str() ); - break; - } - } - } - } - } - - if ( grantStarting ) - { - _OnlineServices.insert( serviceId ); - } - return grantStarting; -} - - -/* - * Release a service instance - */ -void CServiceInstanceManager::releaseService( NLNET::TServiceId serviceId ) -{ - _OnlineServices.erase( serviceId ); // not a problem if not found -} - - -/* - * Display information - */ -void CServiceInstanceManager::displayInfo( NLMISC::CLog *log ) const -{ - log->displayNL( "Restricted services:" ); - std::map< std::string, bool >::const_iterator ius; - for ( ius=_UniqueServices.begin(); ius!=_UniqueServices.end(); ++ius ) - { - log->displayNL( "%s -> only one per %s", (*ius).first.c_str(), (*ius).second?"shard":"machine" ); - } - log->displayNL( "Online registered services:" ); - std::set< TServiceId >::const_iterator ios; - for ( ios=_OnlineServices.begin(); ios!=_OnlineServices.end(); ++ios ) - { - log->displayNL( "%s", CUnifiedNetwork::getInstance()->getServiceUnifiedName( *ios ).c_str() ); - } -} - - -/* - * Make all controlled services quit - */ -void CServiceInstanceManager::killAllServices() -{ - // Send to all known online services - std::set< TServiceId >::const_iterator ios; - for ( ios=_OnlineServices.begin(); ios!=_OnlineServices.end(); ++ios ) - { - doUnregisterService( (TServiceId)(*ios) ); - } -} - - - -// -// Variables -// - -list RegisteredServices; /// List of all registred services - -uint16 MinBasePort = 51000; /// Ports begin at 51000 -uint16 MaxBasePort = 52000; /// (note: in this implementation there can be no more than 1000 services) - -const TServiceId BaseSId(128); /// Allocated SIds begin at 128 (except for Agent Service) - -const TTime UnregisterTimeout = 10000; /// After 10s we remove an unregister service if every server didn't ACK the message - -CCallbackServer *CallbackServer = NULL; - -// -// Functions -// - -bool canAccess (const vector &addr, const CServiceEntry &entry, vector &accessibleAddr) -{ - accessibleAddr.clear (); - - if (entry.WaitingUnregistration) - return false; - - for (uint i = 0; i < addr.size(); i++) - { - uint32 net = addr[i].internalNetAddress(); - for (uint j = 0; j < entry.Addr.size(); j++) - { - if (net == entry.Addr[j].internalNetAddress()) - { - accessibleAddr.push_back (entry.Addr[j]); - } - } - } - - if (accessibleAddr.empty()) - { - nldebug ("service %s-%hu is not accessible by '%s'", entry.Name.c_str(), entry.SId.get(), vectorCInetAddressToString (addr).c_str ()); - } - else - { - nldebug ("service %s-%hu is accessible by '%s'", entry.Name.c_str(), entry.SId.get(), vectorCInetAddressToString (accessibleAddr).c_str ()); - } - - return !accessibleAddr.empty (); -} - -void displayRegisteredServices (CLog *log = InfoLog) -{ - log->displayNL ("Display the %d registered services :", RegisteredServices.size()); - for (list::iterator it = RegisteredServices.begin(); it != RegisteredServices.end (); it++) - { - TSockId id = (*it).SockId; - if (id == NULL) - { - log->displayNL ("> %s-%hu %s '%s' %s %d addr", (*it).Name.c_str(), it->SId.get(), "", "", (*it).WaitingUnregistration?"WaitUnreg":"", (*it).Addr.size()); - for(uint i = 0; i < (*it).Addr.size(); i++) - log->displayNL (" '%s'", (*it).Addr[i].asString().c_str()); - } - else - { - log->displayNL ("> %s-%hu %s '%s' %s %d addr", (*it).Name.c_str(), it->SId.get(), (*it).SockId->asString().c_str(), CallbackServer->hostAddress((*it).SockId).asString().c_str(), (*it).WaitingUnregistration?"WaitUnreg":"", (*it).Addr.size()); - for(uint i = 0; i < (*it).Addr.size(); i++) - log->displayNL (" '%s'", (*it).Addr[i].asString().c_str()); - } - } - log->displayNL ("End of the list"); -} - - -list::iterator effectivelyRemove (list::iterator &it) -{ - // remove the service from the registered service list - nlinfo ("Effectively remove the service %s-%hu", (*it).Name.c_str(), it->SId.get()); - return RegisteredServices.erase (it); -} - -/* - * Helper procedure for cbLookupAlternate and cbUnregister. - * Note: name is used for a LOGS. - */ -list::iterator doRemove (list::iterator it) -{ - nldebug ("Unregister the service %s-%hu '%s'", (*it).Name.c_str(), it->SId.get(), (*it).Addr[0].asString().c_str()); - - // tell to everybody that this service is unregistered - - CMessage msgout ("UNB"); - msgout.serial ((*it).Name); - msgout.serial ((*it).SId); - - vector accessibleAddress; - nlinfo ("Broadcast the Unregistration of %s-%hu to all registered services", (*it).Name.c_str(), it->SId.get()); - for (list::iterator it3 = RegisteredServices.begin(); it3 != RegisteredServices.end (); it3++) - { - if (canAccess((*it).Addr, (*it3), accessibleAddress)) - { - CallbackServer->send (msgout, (*it3).SockId); - //CNetManager::send ("NS", msgout, (*it3).SockId); - nldebug ("Broadcast to %s-%hu", (*it3).Name.c_str(), it3->SId.get()); - } - } - - // new system, after the unregistation broadcast, we wait ACK from all services before really remove - // the service, before, we tag the service as 'wait before unregister' - // if everybody didn't answer before the time out, we remove it - - (*it).SockId = NULL; - - (*it).WaitingUnregistration = true; - (*it).WaitingUnregistrationTime = CTime::getLocalTime(); - - // we remove all services awaiting his ACK because this service is down so it'll never ACK - for (list::iterator itr = RegisteredServices.begin(); itr != RegisteredServices.end (); itr++) - { - for (list::iterator itw = (*itr).WaitingUnregistrationServices.begin(); itw != (*itr).WaitingUnregistrationServices.end ();) - { - if ((*itw) == (*it).SId) - { - itw = (*itr).WaitingUnregistrationServices.erase (itw); - } - else - { - itw++; - } - } - } - - string res; - for (list::iterator it2 = RegisteredServices.begin(); it2 != RegisteredServices.end (); it2++) - { - if (!(*it2).WaitingUnregistration) - { - (*it).WaitingUnregistrationServices.push_back ((*it2).SId); - res += toString((*it2).SId.get()) + " "; - } - } - - nlinfo ("Before removing the service %s-%hu, we wait the ACK of '%s'", (*it).Name.c_str(), (*it).SId.get(), res.c_str()); - - if ((*it).WaitingUnregistrationServices.empty()) - { - return effectivelyRemove (it); - } - else - { - return ++it; - } - - // Release from the service instance manager - SIMInstance->releaseService( (*it).SId ); -} - -void doUnregisterService (TServiceId sid) -{ - list::iterator it; - for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++) - { - if ((*it).SId == sid) - { - // found it, remove it - doRemove (it); - return; - } - } - nlwarning ("Service %hu not found", sid.get()); -} - -void doUnregisterService (TSockId from) -{ - list::iterator it; - for (it = RegisteredServices.begin(); it != RegisteredServices.end ();) - { - if ((*it).SockId == from) - { - // it's possible that one "from" have more than one registred service, so we have to find in all the list - // found it, remove it - it = doRemove (it); - } - else - { - it++; - } - } -} - -/*void doUnregisterService (const CInetAddress &addr) -{ - list::iterator it; - for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++) - { - if ((*it).Addr == addr) - { - // found it, remove it - doRemove (it); - return; - } - } - nlwarning ("Service %s not found", addr.asString().c_str()); -}*/ - -/* - * Helper function for cbRegister. - * If alloc_sid is true, sid is ignored - * Returns false in case of failure of sid allocation or bad sid provided - * Note: the reply is included in this function, because it must be done before things such as syncUniTime() - */ -bool doRegister (const string &name, const vector &addr, TServiceId sid, TSockId from, CCallbackNetBase &netbase, bool reconnection = false) -{ - // Find if the service is not already registered - string reason; - uint8 ok = true; - bool needRegister = true; - /*for (list::iterator it = RegisteredServices.begin(); it != RegisteredServices.end (); it++) - { - if ((*it).Addr.asIPString() == addr.asIPString() ) - { - // we already have a service on this address, remplace it if it's the same name - if ((*it).Name == name) - { - // it's the same service, replace it - (*it).SockId = from; - sid = (*it).SId; - nlinfo ("Replace the service %s", name.c_str()); - } - else - { - nlwarning ("Try to register %s to %s but the service %s already on this address. ignore it!", name.c_str(), addr.asIPString().c_str(), (*it).Name.c_str()); - ok = false; - } - needRegister = false; - break; - } - }*/ - - if (needRegister) - { - if (sid.get() == 0) - { - // we have to find a sid - sid = BaseSId; - bool found = false; - while (!found) - { - list::iterator it; - for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++) - { - if ((*it).SId == sid) - { - break; - } - } - if (it == RegisteredServices.end ()) - { - // ok, we have an empty sid - found = true; - } - else - { - sid.set(sid.get()+1); - if (sid.get() == 0) // round the clock - { - nlwarning ("Service identifier allocation overflow"); - ok = false; - break; - } - } - } - - } - else - { - // we have to check that the user provided sid is available - list::iterator it; - for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++) - { - if ((*it).SId == sid) - { - nlwarning ("Sid %d already used by another service", sid.get()); - ok = false; - break; - } - } - if (it != RegisteredServices.end ()) - { - ok = true; - } - } - - // if ok, register the service and send a broadcast to other people - if (ok) - { - // Check if the instance is allowed to start, according to the restriction in the config file - if ( SIMInstance->queryStartService( name, sid, addr, reason ) ) - { - // add him in the registered list - RegisteredServices.push_back (CServiceEntry(from, addr, name, sid)); - - // tell to everybody but not him that this service is registered - if (!reconnection) - { - CMessage msgout ("RGB"); - TServiceId::size_type s = 1; - msgout.serial (s); - msgout.serial (const_cast(name)); - msgout.serial (sid); - // we need to send all addr to all services even if the service can't access because we use the address index - // to know which connection comes. - msgout.serialCont (const_cast &>(addr)); - nlinfo ("The service is %s-%d, broadcast the Registration to everybody", name.c_str(), sid.get()); - - vector accessibleAddress; - for (list::iterator it3 = RegisteredServices.begin(); it3 != RegisteredServices.end (); it3++) - { - // send only services that can be accessed and not itself - if ((*it3).SId != sid && canAccess(addr, (*it3), accessibleAddress)) - { - CallbackServer->send (msgout, (*it3).SockId); - //CNetManager::send ("NS", msgout, (*it3).SockId); - nldebug ("Broadcast to %s-%hu", (*it3).Name.c_str(), it3->SId.get()); - } - } - } - - // set the sid only if it s ok - from->setAppId (sid.get()); - } - else - { - // Reply "startup denied", and do not send registration to other services - ok = false; - } - } - - // send the message to the service to say if it s ok or not - if (!reconnection) - { - // send the answer to the client - CMessage msgout ("RG"); - msgout.serial (ok); - if (ok) - { - msgout.serial (sid); - - // send him all services available (also itself) - TServiceId::size_type nb = 0; - - vector accessibleAddress; - - for (list::iterator it2 = RegisteredServices.begin(); it2 != RegisteredServices.end (); it2++) - { - // send only services that are available - if (canAccess(addr, (*it2), accessibleAddress)) - nb++; - } - msgout.serial (nb); - - for (list::iterator it = RegisteredServices.begin(); it != RegisteredServices.end (); it++) - { - // send only services that are available - if (canAccess(addr, (*it), accessibleAddress)) - { - msgout.serial ((*it).Name); - msgout.serial ((*it).SId); - msgout.serialCont ((*it).Addr); - } - } - } - else - { - msgout.serial( reason ); - } - - netbase.send (msgout, from); - netbase.flush (from); - } - } - - //displayRegisteredServices (); - - return ok!=0; -} - -void checkWaitingUnregistrationServices () -{ - for (list::iterator it = RegisteredServices.begin(); it != RegisteredServices.end ();) - { - if ((*it).WaitingUnregistration && ((*it).WaitingUnregistrationServices.empty() || CTime::getLocalTime() > (*it).WaitingUnregistrationTime + UnregisterTimeout)) - { - if ((*it).WaitingUnregistrationServices.empty()) - { - nlinfo ("Removing the service %s-%hu because all services ACKd the removal", (*it).Name.c_str(), (*it).SId.get()); - } - else - { - string res; - for (list::iterator it2 = (*it).WaitingUnregistrationServices.begin(); it2 != (*it).WaitingUnregistrationServices.end (); it2++) - { - res += toString(it2->get()) + " "; - } - nlwarning ("Removing the service %s-%hu because time out occurs (service numbers %s didn't ACK)", (*it).Name.c_str(), (*it).SId.get(), res.c_str()); - } - it = effectivelyRemove (it); - } - else - { - it++; - } - } -} - - -/** - * Callback for service unregistration ACK. Mean that a service was ACK the unregistration broadcast - */ -static void cbACKUnregistration (CMessage& msgin, TSockId from, CCallbackNetBase &netbase) -{ - TServiceId sid; - msgin.serial (sid); - - for (list::iterator it = RegisteredServices.begin(); it != RegisteredServices.end (); it++) - { - if ((*it).SId == sid && (*it).WaitingUnregistration) - { - for (list::iterator it2 = (*it).WaitingUnregistrationServices.begin(); it2 != (*it).WaitingUnregistrationServices.end (); it2++) - { - if (*it2 == TServiceId(uint16(from->appId()))) - { - // remove the acked service - (*it).WaitingUnregistrationServices.erase (it2); - checkWaitingUnregistrationServices (); - return; - } - } - } - } -} - - -/** - * Callback for service registration when the naming service goes down and up (don't need to broadcast) - */ -static void cbResendRegisteration (CMessage& msgin, TSockId from, CCallbackNetBase &netbase) -{ - string name; - vector addr; - TServiceId sid; - msgin.serial (name); - msgin.serialCont (addr); - msgin.serial (sid); - - doRegister (name, addr, sid, from, netbase, true); -} - - - -/** - * Callback for service registration. - * - * Message expected : RG - * - Name of service to register (string) - * - Address of service (CInetAddress) - * - * Message emitted : RG - * - Allocated service identifier (TServiceId) or 0 if failed - */ -static void cbRegister (CMessage& msgin, TSockId from, CCallbackNetBase &netbase) -{ - string name; - vector addr; - TServiceId sid; - msgin.serial (name); - msgin.serialCont (addr); - msgin.serial (sid); - - doRegister (name, addr, sid, from, netbase); -} - - -/** - * Callback for service unregistration. - * - * Message expected : UNI - * - Service identifier (TServiceId) - */ -static void cbUnregisterSId (CMessage& msgin, TSockId from, CCallbackNetBase &netbase) -{ - TServiceId sid; - msgin.serial( sid ); - - doUnregisterService (sid); - //displayRegisteredServices (); -} - - -/* - * Helper function for cbQueryPort - * - * \warning QueryPort + Registration is not atomic so more than one service could ask a port before register - */ -uint16 doAllocatePort (const CInetAddress &addr) -{ - static uint16 nextAvailablePort = MinBasePort; - - // check if nextavailableport is free - - if (nextAvailablePort >= MaxBasePort) nextAvailablePort = MinBasePort; - - bool ok; - do - { - ok = true; - list::iterator it; - for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++) - { - if ((*it).Addr[0].port () == nextAvailablePort) - { - nextAvailablePort++; - ok = false; - break; - } - } - } - while (!ok); - - return nextAvailablePort++; -} - - -/** - * Callback for port allocation - * Note: if a service queries a port but does not register itself to the naming service, the - * port will remain allocated and unused. - * - * Message expected : QP - * - Name of service to register (string) - * - Address of service (CInetAddress) (its port can be 0) - * - * Message emitted : QP - * - Allocated port number (uint16) - */ -static void cbQueryPort (CMessage& msgin, TSockId from, CCallbackNetBase &netbase) -{ - // Allocate port - uint16 port = doAllocatePort (netbase.hostAddress (from)); - - // Send port back - CMessage msgout ("QP"); - msgout.serial (port); - netbase.send (msgout, from); - - nlinfo ("The service got port %hu", port); -} - - -/* - * Unregisters a service if it has not been done before. - * Note: this callback is called whenever someone disconnects from the NS. - * May be there are too many calls if many clients perform many transactional lookups. - */ -static void cbDisconnect /*(const string &serviceName, TSockId from, void *arg)*/ ( TSockId from, void *arg ) -{ - doUnregisterService (from); - //displayRegisteredServices (); -} - -/* - * a service is connected, send him all services infos - */ -static void cbConnect /*(const string &serviceName, TSockId from, void *arg)*/ ( TSockId from, void *arg ) -{ - // we have to wait the registred services message to send all services because it this points, we can't know which sub net - // the service can use - - //displayRegisteredServices (); - - // set the appid with a bad id (-1) - from->setAppId (~0); -} - -/*// returns the list of accessible services with a list of address -static void cbRegisteredServices(CMessage& msgin, TSockId from, CCallbackNetBase &netbase) -{ - vector addr; - msgin.serialCont (addr); - - nlinfo ("New service ask me the available services, sending him all services available"); - // send to the new service the list of all services that this service can access (depending of his sub net) - - CMessage msgout ("RGB"); - - uint8 nb = 0; - - vector accessibleAddress; - - for (list::iterator it2 = RegisteredServices.begin(); it2 != RegisteredServices.end (); it2++) - { - // send only services that are available - if (canAccess(addr, (*it2), accessibleAddress)) - nb++; - } - - msgout.serial (nb); - - for (list::iterator it = RegisteredServices.begin(); it != RegisteredServices.end (); it++) - { - // send only services that are available - if (canAccess(addr, (*it), accessibleAddress)) - { - msgout.serial ((*it).Name); - msgout.serial ((*it).SId); - msgout.serialCont (accessibleAddress); - } - } - - CNetManager::send ("NS", msgout, from); -}*/ - - -/* - * Helper that emulates layer5 send() - */ -/*void sendToService( uint16 sid, CMessage& msgout ) -{ - list::iterator it; - for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++) - { - if ((*it).SId == sid) - { - CallbackServer->send (msgout, (*it).SockId); - } - } -}*/ - - -/* - * Helper that emulate layer5's getServiceName() - */ -string getServiceName( TServiceId sid ) -{ - list::iterator it; - for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++) - { - if ((*it).SId == sid) - { - return (*it).Name; - } - } - return ""; // not found -} - - -/* - * Helper that returns the first address of a service - */ -CInetAddress getHostAddress( TServiceId sid ) -{ - list::iterator it; - for (it = RegisteredServices.begin(); it != RegisteredServices.end (); it++) - { - if ((*it).SId == sid) - { - return (*it).Addr[0]; - } - } - return CInetAddress(); -} - - -// -// Callback array -// - -TCallbackItem CallbackArray[] = -{ - { "RG", cbRegister }, - { "RRG", cbResendRegisteration }, - { "QP", cbQueryPort }, - { "UNI", cbUnregisterSId }, - { "ACK_UNI", cbACKUnregistration }, -// { "RS", cbRegisteredServices }, -}; - - -// -// Service -// - -class CNamingService : public NLNET::IService -{ -public: - - /** - * Init - */ - void init() - { - // if a baseport is available in the config file, get it - CConfigFile::CVar *var; - if ((var = ConfigFile.getVarPtr ("BasePort")) != NULL) - { - uint16 newBasePort = var->asInt (); - nlinfo ("Changing the MinBasePort number from %hu to %hu", MinBasePort, newBasePort); - sint32 delta = MaxBasePort - MinBasePort; - nlassert (delta > 0); - MinBasePort = newBasePort; - MaxBasePort = MinBasePort + uint16 (delta); - } - - // Parameters for the service instance manager - try - { - CConfigFile::CVar& uniqueServices = ConfigFile.getVar("UniqueOnShardServices"); - for ( uint i=0; i!=uniqueServices.size(); ++i ) - { - _ServiceInstances.addUniqueService( uniqueServices.asString(i), true ); - } - } - catch(Exception &) - {} - try - { - CConfigFile::CVar& uniqueServicesM = ConfigFile.getVar("UniqueByMachineServices"); - for ( uint i=0; i!=uniqueServicesM.size(); ++i ) - { - _ServiceInstances.addUniqueService( uniqueServicesM.asString(i), false ); - } - } - catch(Exception &) - {} - -/* - // we don't try to associate message from client - CNetManager::getNetBase ("NS")->ignoreAllUnknownId (true); - - // add the callback in case of disconnection - CNetManager::setConnectionCallback ("NS", cbConnect, NULL); - - // add the callback in case of disconnection - CNetManager::setDisconnectionCallback ("NS", cbDisconnect, NULL); -*/ - // DEBUG - // DebugLog->addDisplayer( new CStdDisplayer() ); - - vector v = CInetAddress::localAddresses(); - nlinfo ("%d detected local addresses:", v.size()); - for (uint i = 0; i < v.size(); i++) - { - nlinfo (" %d - '%s'",i, v[i].asString().c_str()); - } - - uint16 nsport = 50000; - if ((var = ConfigFile.getVarPtr ("NSPort")) != NULL) - { - nsport = var->asInt (); - } - - CallbackServer = new CCallbackServer; - CallbackServer->init(nsport); - CallbackServer->addCallbackArray(CallbackArray, sizeof(CallbackArray)/sizeof(CallbackArray[0])); - CallbackServer->setConnectionCallback(cbConnect, NULL); - CallbackServer->setDisconnectionCallback(cbDisconnect, NULL); - } - - /** - * Update - */ - bool update () - { - checkWaitingUnregistrationServices (); - - CallbackServer->update (); - - return true; - } - - void release() - { - if (CallbackServer != NULL) - delete CallbackServer; - CallbackServer = NULL; - } - -private: - - /// Service instance manager singleton - CServiceInstanceManager _ServiceInstances; -}; - - static const char* getCompleteServiceName(const IService* theService) { static std::string s; @@ -1147,12 +162,12 @@ NLMISC_DYNVARIABLE(uint32, NbRegisteredServices, "display the number of service NLMISC_COMMAND( displayServiceInstances, "SIM: Display info on service instances", "" ) { - SIMInstance->displayInfo( &log ); + CServiceInstanceManager::getInstance()->displayInfo( &log ); return true; } NLMISC_COMMAND( killAllServices, "SIM: Make all the controlled services quit", "" ) { - SIMInstance->killAllServices(); + CServiceInstanceManager::getInstance()->killAllServices(); return true; } diff --git a/nelns/naming_service/nelns/naming_service/functions.cpp b/nelns/naming_service/nelns/naming_service/functions.cpp new file mode 100644 index 0000000000..32d533070a --- /dev/null +++ b/nelns/naming_service/nelns/naming_service/functions.cpp @@ -0,0 +1,572 @@ +#include + +#include + +#include +#include + +#include +#include + +using std::list; +using std::string; +using std::vector; + +using NLMISC::CLog; +using NLMISC::CTime; +using NLMISC::InfoLog; +using NLMISC::toString; +using NLNET::CCallbackNetBase; +using NLNET::CInetAddress; +using NLNET::CMessage; +using NLNET::TServiceId; +using NLNET::TSockId; + +// +// Functions +// + +bool canAccess(const vector &addr, const CServiceEntry &entry, vector &accessibleAddr) +{ + accessibleAddr.clear(); + + if (entry.WaitingUnregistration) + return false; + + for (uint i = 0; i < addr.size(); i++) + { + uint32 net = addr[i].internalNetAddress(); + for (uint j = 0; j < entry.Addr.size(); j++) + { + if (net == entry.Addr[j].internalNetAddress()) + { + accessibleAddr.push_back(entry.Addr[j]); + } + } + } + + if (accessibleAddr.empty()) + { + nldebug("service %s-%hu is not accessible by '%s'", entry.Name.c_str(), entry.SId.get(), vectorCInetAddressToString(addr).c_str()); + } + else + { + nldebug("service %s-%hu is accessible by '%s'", entry.Name.c_str(), entry.SId.get(), vectorCInetAddressToString(accessibleAddr).c_str()); + } + + return !accessibleAddr.empty(); +} + +void displayRegisteredServices(CLog *log) +{ + log->displayNL("Display the %d registered services :", RegisteredServices.size()); + for (list::iterator it = RegisteredServices.begin(); it != RegisteredServices.end(); it++) + { + TSockId id = (*it).SockId; + if (id == NULL) + { + log->displayNL("> %s-%hu %s '%s' %s %d addr", (*it).Name.c_str(), it->SId.get(), "", "", (*it).WaitingUnregistration ? "WaitUnreg" : "", (*it).Addr.size()); + for (uint i = 0; i < (*it).Addr.size(); i++) + log->displayNL(" '%s'", (*it).Addr[i].asString().c_str()); + } + else + { + log->displayNL("> %s-%hu %s '%s' %s %d addr", (*it).Name.c_str(), it->SId.get(), (*it).SockId->asString().c_str(), CallbackServer->hostAddress((*it).SockId).asString().c_str(), (*it).WaitingUnregistration ? "WaitUnreg" : "", (*it).Addr.size()); + for (uint i = 0; i < (*it).Addr.size(); i++) + log->displayNL(" '%s'", (*it).Addr[i].asString().c_str()); + } + } + log->displayNL("End of the list"); +} + +list::iterator effectivelyRemove(list::iterator &it) +{ + // remove the service from the registered service list + nlinfo("Effectively remove the service %s-%hu", (*it).Name.c_str(), it->SId.get()); + return RegisteredServices.erase(it); +} + +/* + * Helper procedure for cbLookupAlternate and cbUnregister. + * Note: name is used for a LOGS. + */ +list::iterator doRemove(list::iterator it) +{ + nldebug("Unregister the service %s-%hu '%s'", (*it).Name.c_str(), it->SId.get(), (*it).Addr[0].asString().c_str()); + + // tell to everybody that this service is unregistered + + CMessage msgout("UNB"); + msgout.serial((*it).Name); + msgout.serial((*it).SId); + + vector accessibleAddress; + nlinfo("Broadcast the Unregistration of %s-%hu to all registered services", (*it).Name.c_str(), it->SId.get()); + for (list::iterator it3 = RegisteredServices.begin(); it3 != RegisteredServices.end(); it3++) + { + if (canAccess((*it).Addr, (*it3), accessibleAddress)) + { + CallbackServer->send(msgout, (*it3).SockId); + // CNetManager::send ("NS", msgout, (*it3).SockId); + nldebug("Broadcast to %s-%hu", (*it3).Name.c_str(), it3->SId.get()); + } + } + + // new system, after the unregistation broadcast, we wait ACK from all services before really remove + // the service, before, we tag the service as 'wait before unregister' + // if everybody didn't answer before the time out, we remove it + + (*it).SockId = NULL; + + (*it).WaitingUnregistration = true; + (*it).WaitingUnregistrationTime = CTime::getLocalTime(); + + // we remove all services awaiting his ACK because this service is down so it'll never ACK + for (list::iterator itr = RegisteredServices.begin(); itr != RegisteredServices.end(); itr++) + { + for (list::iterator itw = (*itr).WaitingUnregistrationServices.begin(); itw != (*itr).WaitingUnregistrationServices.end();) + { + if ((*itw) == (*it).SId) + { + itw = (*itr).WaitingUnregistrationServices.erase(itw); + } + else + { + itw++; + } + } + } + + string res; + for (list::iterator it2 = RegisteredServices.begin(); it2 != RegisteredServices.end(); it2++) + { + if (!(*it2).WaitingUnregistration) + { + (*it).WaitingUnregistrationServices.push_back((*it2).SId); + res += toString((*it2).SId.get()) + " "; + } + } + + nlinfo("Before removing the service %s-%hu, we wait the ACK of '%s'", (*it).Name.c_str(), (*it).SId.get(), res.c_str()); + + if ((*it).WaitingUnregistrationServices.empty()) + { + return effectivelyRemove(it); + } + else + { + return ++it; + } + + // Release from the service instance manager + CServiceInstanceManager::getInstance()->releaseService((*it).SId); +} + +void doUnregisterService(const TServiceId &sid) +{ + list::iterator it; + for (it = RegisteredServices.begin(); it != RegisteredServices.end(); it++) + { + if ((*it).SId == sid) + { + // found it, remove it + doRemove(it); + return; + } + } + nlwarning("Service %hu not found", sid.get()); +} + +void doUnregisterService(const NLNET::TSockId &from) +{ + list::iterator it; + for (it = RegisteredServices.begin(); it != RegisteredServices.end();) + { + if ((*it).SockId == from) + { + // it's possible that one "from" have more than one registred service, so we have to find in all the list + // found it, remove it + it = doRemove(it); + } + else + { + it++; + } + } +} + +/* + * Helper function for cbRegister. + * If alloc_sid is true, sid is ignored + * Returns false in case of failure of sid allocation or bad sid provided + * Note: the reply is included in this function, because it must be done before things such as syncUniTime() + */ +bool doRegister(const string &name, const vector &addr, TServiceId sid, TSockId from, CCallbackNetBase &netbase, bool reconnection) +{ + // Find if the service is not already registered + string reason; + uint8 ok = true; + + if (sid.get() == 0) + { + // we have to find a sid + sid = BaseSId; + bool found = false; + while (!found) + { + list::iterator it; + for (it = RegisteredServices.begin(); it != RegisteredServices.end(); it++) + { + if ((*it).SId == sid) + { + break; + } + } + if (it == RegisteredServices.end()) + { + // ok, we have an empty sid + found = true; + } + else + { + sid.set(sid.get() + 1); + if (sid.get() == 0) // round the clock + { + nlwarning("Service identifier allocation overflow"); + ok = false; + break; + } + } + } + } + else + { + // we have to check that the user provided sid is available + list::iterator it; + for (it = RegisteredServices.begin(); it != RegisteredServices.end(); it++) + { + if ((*it).SId == sid) + { + nlwarning("Sid %d already used by another service", sid.get()); + ok = false; + break; + } + } + if (it != RegisteredServices.end()) + { + ok = true; + } + } + + // if ok, register the service and send a broadcast to other people + if (ok) + { + // Check if the instance is allowed to start, according to the restriction in the config file + if (CServiceInstanceManager::getInstance()->queryStartService(name, sid, addr, reason)) + { + // add him in the registered list + RegisteredServices.push_back(CServiceEntry(from, addr, name, sid)); + + // tell to everybody but not him that this service is registered + if (!reconnection) + { + CMessage msgout("RGB"); + TServiceId::size_type s = 1; + msgout.serial(s); + msgout.serial(const_cast(name)); + msgout.serial(sid); + // we need to send all addr to all services even if the service can't access because we use the address index + // to know which connection comes. + msgout.serialCont(const_cast &>(addr)); + nlinfo("The service is %s-%d, broadcast the Registration to everybody", name.c_str(), sid.get()); + + vector accessibleAddress; + for (list::iterator it3 = RegisteredServices.begin(); it3 != RegisteredServices.end(); it3++) + { + // send only services that can be accessed and not itself + if ((*it3).SId != sid && canAccess(addr, (*it3), accessibleAddress)) + { + CallbackServer->send(msgout, (*it3).SockId); + // CNetManager::send ("NS", msgout, (*it3).SockId); + nldebug("Broadcast to %s-%hu", (*it3).Name.c_str(), it3->SId.get()); + } + } + } + + // set the sid only if it s ok + from->setAppId(sid.get()); + } + else + { + // Reply "startup denied", and do not send registration to other services + ok = false; + } + } + + // send the message to the service to say if it s ok or not + if (!reconnection) + { + // send the answer to the client + CMessage msgout("RG"); + msgout.serial(ok); + if (ok) + { + msgout.serial(sid); + + // send him all services available (also itself) + TServiceId::size_type nb = 0; + + vector accessibleAddress; + + for (list::iterator it2 = RegisteredServices.begin(); it2 != RegisteredServices.end(); it2++) + { + // send only services that are available + if (canAccess(addr, (*it2), accessibleAddress)) + nb++; + } + msgout.serial(nb); + + for (list::iterator it = RegisteredServices.begin(); it != RegisteredServices.end(); it++) + { + // send only services that are available + if (canAccess(addr, (*it), accessibleAddress)) + { + msgout.serial((*it).Name); + msgout.serial((*it).SId); + msgout.serialCont((*it).Addr); + } + } + } + else + { + msgout.serial(reason); + } + + netbase.send(msgout, from); + netbase.flush(from); + } + + // displayRegisteredServices (); + + return ok != 0; +} + +void checkWaitingUnregistrationServices() +{ + for (list::iterator it = RegisteredServices.begin(); it != RegisteredServices.end();) + { + if ((*it).WaitingUnregistration && ((*it).WaitingUnregistrationServices.empty() || CTime::getLocalTime() > (*it).WaitingUnregistrationTime + UnregisterTimeout)) + { + if ((*it).WaitingUnregistrationServices.empty()) + { + nlinfo("Removing the service %s-%hu because all services ACKd the removal", (*it).Name.c_str(), (*it).SId.get()); + } + else + { + string res; + for (list::iterator it2 = (*it).WaitingUnregistrationServices.begin(); it2 != (*it).WaitingUnregistrationServices.end(); it2++) + { + res += toString(it2->get()) + " "; + } + nlwarning("Removing the service %s-%hu because time out occurs (service numbers %s didn't ACK)", (*it).Name.c_str(), (*it).SId.get(), res.c_str()); + } + it = effectivelyRemove(it); + } + else + { + it++; + } + } +} + +/** + * Callback for service unregistration ACK. Mean that a service was ACK the unregistration broadcast + */ +void cbACKUnregistration(CMessage &msgin, TSockId from, CCallbackNetBase &netbase) +{ + TServiceId sid; + msgin.serial(sid); + + for (list::iterator it = RegisteredServices.begin(); it != RegisteredServices.end(); it++) + { + if ((*it).SId == sid && (*it).WaitingUnregistration) + { + for (list::iterator it2 = (*it).WaitingUnregistrationServices.begin(); it2 != (*it).WaitingUnregistrationServices.end(); it2++) + { + if (*it2 == TServiceId(uint16(from->appId()))) + { + // remove the acked service + (*it).WaitingUnregistrationServices.erase(it2); + checkWaitingUnregistrationServices(); + return; + } + } + } + } +} + +/** + * Callback for service registration when the naming service goes down and up (don't need to broadcast) + */ +void cbResendRegisteration(CMessage &msgin, TSockId from, CCallbackNetBase &netbase) +{ + string name; + vector addr; + TServiceId sid; + msgin.serial(name); + msgin.serialCont(addr); + msgin.serial(sid); + + doRegister(name, addr, sid, from, netbase, true); +} + +/** + * Callback for service registration. + * + * Message expected : RG + * - Name of service to register (string) + * - Address of service (CInetAddress) + * + * Message emitted : RG + * - Allocated service identifier (TServiceId) or 0 if failed + */ +void cbRegister(CMessage &msgin, TSockId from, CCallbackNetBase &netbase) +{ + string name; + vector addr; + TServiceId sid; + msgin.serial(name); + msgin.serialCont(addr); + msgin.serial(sid); + + doRegister(name, addr, sid, from, netbase); +} + +/** + * Callback for service unregistration. + * + * Message expected : UNI + * - Service identifier (TServiceId) + */ +void cbUnregisterSId(CMessage &msgin, TSockId from, CCallbackNetBase &netbase) +{ + TServiceId sid; + msgin.serial(sid); + + doUnregisterService(sid); + // displayRegisteredServices (); +} + +/* + * Helper function for cbQueryPort + * + * \warning QueryPort + Registration is not atomic so more than one service could ask a port before register + */ +uint16 doAllocatePort(const CInetAddress &addr) +{ + static uint16 nextAvailablePort = MinBasePort; + + // check if nextavailableport is free + + if (nextAvailablePort >= MaxBasePort) nextAvailablePort = MinBasePort; + + bool ok; + do + { + ok = true; + list::iterator it; + for (it = RegisteredServices.begin(); it != RegisteredServices.end(); it++) + { + if ((*it).Addr[0].port() == nextAvailablePort) + { + nextAvailablePort++; + ok = false; + break; + } + } + } while (!ok); + + return nextAvailablePort++; +} + +/** + * Callback for port allocation + * Note: if a service queries a port but does not register itself to the naming service, the + * port will remain allocated and unused. + * + * Message expected : QP + * - Name of service to register (string) + * - Address of service (CInetAddress) (its port can be 0) + * + * Message emitted : QP + * - Allocated port number (uint16) + */ +void cbQueryPort(CMessage &msgin, TSockId from, CCallbackNetBase &netbase) +{ + // Allocate port + uint16 port = doAllocatePort(netbase.hostAddress(from)); + + // Send port back + CMessage msgout("QP"); + msgout.serial(port); + netbase.send(msgout, from); + + nlinfo("The service got port %hu", port); +} + +/* + * Unregisters a service if it has not been done before. + * Note: this callback is called whenever someone disconnects from the NS. + * May be there are too many calls if many clients perform many transactional lookups. + */ +void cbDisconnect /*(const string &serviceName, TSockId from, void *arg)*/ (TSockId from, void *arg) +{ + doUnregisterService(from); + // displayRegisteredServices (); +} + +/* + * a service is connected, send him all services infos + */ +void cbConnect /*(const string &serviceName, TSockId from, void *arg)*/ (TSockId from, void *arg) +{ + // we have to wait the registred services message to send all services because it this points, we can't know which sub net + // the service can use + + // displayRegisteredServices (); + + // set the appid with a bad id (-1) + from->setAppId(~0); +} + +/* + * Helper that emulate layer5's getServiceName() + */ +string getServiceName(const TServiceId &sid) +{ + list::iterator it; + for (it = RegisteredServices.begin(); it != RegisteredServices.end(); it++) + { + if ((*it).SId == sid) + { + return (*it).Name; + } + } + return ""; // not found +} + +/* + * Helper that returns the first address of a service + */ +CInetAddress getHostAddress(const TServiceId &sid) +{ + list::iterator it; + for (it = RegisteredServices.begin(); it != RegisteredServices.end(); it++) + { + if ((*it).SId == sid) + { + return (*it).Addr[0]; + } + } + return {}; +} diff --git a/nelns/naming_service/nelns/naming_service/functions.h b/nelns/naming_service/nelns/naming_service/functions.h new file mode 100644 index 0000000000..9959349a9e --- /dev/null +++ b/nelns/naming_service/nelns/naming_service/functions.h @@ -0,0 +1,112 @@ +#ifndef NELNS_NAMING_SERVICE_FUNCTIONS_H +#define NELNS_NAMING_SERVICE_FUNCTIONS_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +bool canAccess(const std::vector &addr, const CServiceEntry &entry, std::vector &accessibleAddr); + +void displayRegisteredServices(NLMISC::CLog *log = NLMISC::InfoLog); + +std::list::iterator effectivelyRemove (std::list::iterator &it); + +/* + * Helper procedure for cbLookupAlternate and cbUnregister. + * Note: name is used for a LOGS. + */ +std::list::iterator doRemove(std::list::iterator it); + +// Asks a service to stop and tell every one +void doUnregisterService(const NLNET::TServiceId &sid); + +void doUnregisterService(const NLNET::TSockId &from); + +/* + * Helper function for cbRegister. + * If alloc_sid is true, sid is ignored + * Returns false in case of failure of sid allocation or bad sid provided + * Note: the reply is included in this function, because it must be done before things such as syncUniTime() + */ +bool doRegister(const std::string &name, const std::vector &addr, NLNET::TServiceId sid, NLNET::TSockId from, NLNET::CCallbackNetBase &netbase, bool reconnection = false); + +void checkWaitingUnregistrationServices(); + +/** + * Callback for service unregistration ACK. Mean that a service was ACK the unregistration broadcast + */ +void cbACKUnregistration(NLNET::CMessage &msgin, NLNET::TSockId from, NLNET::CCallbackNetBase &netbase); + +/** + * Callback for service registration when the naming service goes down and up (don't need to broadcast) + */ +void cbResendRegisteration(NLNET::CMessage &msgin, NLNET::TSockId from, NLNET::CCallbackNetBase &netbase); + +/** + * Callback for service registration. + * + * Message expected : RG + * - Name of service to register (string) + * - Address of service (CInetAddress) + * + * Message emitted : RG + * - Allocated service identifier (TServiceId) or 0 if failed + */ +void cbRegister(NLNET::CMessage &msgin, NLNET::TSockId from, NLNET::CCallbackNetBase &netbase); + +/** + * Callback for service unregistration. + * + * Message expected : UNI + * - Service identifier (TServiceId) + */ +void cbUnregisterSId(NLNET::CMessage &msgin, NLNET::TSockId from, NLNET::CCallbackNetBase &netbase); + +/* + * Helper function for cbQueryPort + * + * \warning QueryPort + Registration is not atomic so more than one service could ask a port before register + */ +uint16 doAllocatePort(const NLNET::CInetAddress &addr); + +/** + * Callback for port allocation + * Note: if a service queries a port but does not register itself to the naming service, the + * port will remain allocated and unused. + * + * Message expected : QP + * - Name of service to register (string) + * - Address of service (CInetAddress) (its port can be 0) + * + * Message emitted : QP + * - Allocated port number (uint16) + */ +void cbQueryPort(NLNET::CMessage &msgin, NLNET::TSockId from, NLNET::CCallbackNetBase &netbase); + +/* + * Unregisters a service if it has not been done before. + * Note: this callback is called whenever someone disconnects from the NS. + * May be there are too many calls if many clients perform many transactional lookups. + */ +void cbDisconnect(NLNET::TSockId from, void *arg); + +/* + * a service is connected, send him all services infos + */ +void cbConnect(NLNET::TSockId from, void *arg); + +// Helper that emulate layer5's getServiceName() +std::string getServiceName(const NLNET::TServiceId& sid); + +// Helper that returns the first address of a service +NLNET::CInetAddress getHostAddress( const NLNET::TServiceId& sid ); + +#endif // NELNS_NAMING_SERVICE_FUNCTIONS_H diff --git a/nelns/naming_service/nelns/naming_service/naming_service.cpp b/nelns/naming_service/nelns/naming_service/naming_service.cpp new file mode 100644 index 0000000000..757d2ad959 --- /dev/null +++ b/nelns/naming_service/nelns/naming_service/naming_service.cpp @@ -0,0 +1,132 @@ +#include + +#include +#include +#include + +#include +#include + +#include +#include + +using std::map; +using std::set; +using std::string; +using std::vector; + +using NLMISC::CConfigFile; +using NLMISC::Exception; +using NLMISC::toString; +using NLNET::CCallbackServer; +using NLNET::CInetAddress; +using NLNET::CUnifiedNetwork; +using NLNET::TCallbackItem; +using NLNET::TServiceId; +using NLNET::TSockId; + +// +// Callback array +// + +TCallbackItem CallbackArray[] = { + { "RG", cbRegister }, + { "RRG", cbResendRegisteration }, + { "QP", cbQueryPort }, + { "UNI", cbUnregisterSId }, + { "ACK_UNI", cbACKUnregistration }, + // { "RS", cbRegisteredServices }, +}; + +/** + * Init + */ +void CNamingService::init() +{ + // if a baseport is available in the config file, get it + CConfigFile::CVar *var; + if ((var = ConfigFile.getVarPtr("BasePort")) != NULL) + { + uint16 newBasePort = var->asInt(); + nlinfo("Changing the MinBasePort number from %hu to %hu", MinBasePort, newBasePort); + sint32 delta = MaxBasePort - MinBasePort; + nlassert(delta > 0); + MinBasePort = newBasePort; + MaxBasePort = MinBasePort + uint16(delta); + } + + // Parameters for the service instance manager + try + { + CConfigFile::CVar &uniqueServices = ConfigFile.getVar("UniqueOnShardServices"); + for (uint i = 0; i != uniqueServices.size(); ++i) + { + _ServiceInstances.addUniqueService(uniqueServices.asString(i), true); + } + } + catch (Exception &) + { + } + try + { + CConfigFile::CVar &uniqueServicesM = ConfigFile.getVar("UniqueByMachineServices"); + for (uint i = 0; i != uniqueServicesM.size(); ++i) + { + _ServiceInstances.addUniqueService(uniqueServicesM.asString(i), false); + } + } + catch (Exception &) + { + } + + /* + // we don't try to associate message from client + CNetManager::getNetBase ("NS")->ignoreAllUnknownId (true); + + // add the callback in case of disconnection + CNetManager::setConnectionCallback ("NS", cbConnect, NULL); + + // add the callback in case of disconnection + CNetManager::setDisconnectionCallback ("NS", cbDisconnect, NULL); + */ + // DEBUG + // DebugLog->addDisplayer( new CStdDisplayer() ); + + vector v = CInetAddress::localAddresses(); + nlinfo("%d detected local addresses:", v.size()); + for (uint i = 0; i < v.size(); i++) + { + nlinfo(" %d - '%s'", i, v[i].asString().c_str()); + } + + uint16 nsport = 50000; + if ((var = ConfigFile.getVarPtr("NSPort")) != NULL) + { + nsport = var->asInt(); + } + + CallbackServer = new CCallbackServer; + CallbackServer->init(nsport); + CallbackServer->addCallbackArray(CallbackArray, sizeof(CallbackArray) / sizeof(CallbackArray[0])); + CallbackServer->setConnectionCallback(cbConnect, NULL); + CallbackServer->setDisconnectionCallback(cbDisconnect, NULL); +} + +/** + * Update + */ +bool CNamingService::update() +{ + checkWaitingUnregistrationServices(); + + CallbackServer->update(); + + return true; +} + +void CNamingService::release() +{ + if (CallbackServer != NULL) + delete CallbackServer; + CallbackServer = NULL; +} diff --git a/nelns/naming_service/nelns/naming_service/naming_service.h b/nelns/naming_service/nelns/naming_service/naming_service.h new file mode 100644 index 0000000000..c4c41babe3 --- /dev/null +++ b/nelns/naming_service/nelns/naming_service/naming_service.h @@ -0,0 +1,33 @@ +#ifndef NELNS_NAMING_SERVICE_SERVICE_NAMING_SERVICE_H +#define NELNS_NAMING_SERVICE_SERVICE_NAMING_SERVICE_H + +#include +#include + +#include + +// +// Service +// + +class CNamingService : public NLNET::IService +{ +public: + /** + * Init + */ + void init(); + + /** + * Update + */ + bool update(); + + void release(); + +private: + /// Service instance manager singleton + CServiceInstanceManager _ServiceInstances; +}; + +#endif // NELNS_NAMING_SERVICE_SERVICE_NAMING_SERVICE_H diff --git a/nelns/naming_service/nelns/naming_service/service_entry.h b/nelns/naming_service/nelns/naming_service/service_entry.h new file mode 100644 index 0000000000..87ca9ac2e2 --- /dev/null +++ b/nelns/naming_service/nelns/naming_service/service_entry.h @@ -0,0 +1,36 @@ +#ifndef NL_SERVICE_ENTRY_H +#define NL_SERVICE_ENTRY_H + +#include +#include + +#include + +#include +#include +#include + +struct CServiceEntry +{ + CServiceEntry(NLNET::TSockId sock, const std::vector &a, const std::string &n, const NLNET::TServiceId &s) + : SockId(sock) + , Addr(a) + , Name(n) + , SId(s) + , WaitingUnregistration(false) + { + } + + NLNET::TSockId SockId; // the connection between the service and the naming service + std::vector Addr; // address to send to the service who wants to lookup this service + // it s possible to have more than one addr, anyway, the naming service + // will send good address depending of the sub net address of the service + std::string Name; // name of the service + NLNET::TServiceId SId; // id of the service + + bool WaitingUnregistration; // true if this service is in unregistration process (wait other service ACK) + NLMISC::TTime WaitingUnregistrationTime; // time of the beginning of the inregistration process + std::list WaitingUnregistrationServices; // list of service that we wait the answer +}; + +#endif // NL_SERVICE_ENTRY_H diff --git a/nelns/naming_service/nelns/naming_service/service_instance_manager.cpp b/nelns/naming_service/nelns/naming_service/service_instance_manager.cpp new file mode 100644 index 0000000000..342de2697b --- /dev/null +++ b/nelns/naming_service/nelns/naming_service/service_instance_manager.cpp @@ -0,0 +1,134 @@ +#include + +#include + +#include + +using std::map; +using std::set; +using std::string; +using std::vector; + +using NLMISC::toString; +using NLNET::CInetAddress; +using NLNET::CUnifiedNetwork; +using NLNET::TServiceId; + +CServiceInstanceManager *CServiceInstanceManager::_Instance = nullptr; + +CServiceInstanceManager *CServiceInstanceManager::getInstance() +{ + nlassertex(_Instance, ("No Singleton Instance existing")); + return _Instance; +} + +/* + * Constructor + */ +CServiceInstanceManager::CServiceInstanceManager() +{ + nlassertex(!_Instance, ("Singleton Instance already existing")); + _Instance = this; + + // Note: addCallbackArray() done in CRangeMirrorManager::init() +} + +CServiceInstanceManager::~CServiceInstanceManager() +{ + _Instance = nullptr; +} + +/* + * Check if a service is allowed to start. Answer with a GSTS (Grant Start Service) message + */ +bool CServiceInstanceManager::queryStartService(const string &serviceName, TServiceId serviceId, const vector &addr, string &reason) +{ + bool grantStarting = true; + map::iterator ius = _UniqueServices.find(serviceName); + if (ius != _UniqueServices.end()) + { + // Service is restricted + set::iterator ios; + bool uniqueOnShard = (*ius).second; + for (ios = _OnlineServices.begin(); ios != _OnlineServices.end(); ++ios) + { + string name = getServiceName(*ios); + if (name == serviceName) + { + if (uniqueOnShard) + { + // Only one service by shard is allowed => deny + grantStarting = false; + reason = toString("Service %s already found as %hu, must be unique on shard", serviceName.c_str(), ios->get()); + nlinfo(reason.c_str()); + break; + } + else + { + // Only one service by physical machine is allowed + + // Implementation for layer5 + // TSockId hostid1, hostid2; + /*CCallbackNetBase *cnb1 = CUnifiedNetwork::getInstance()->getNetBase( serviceId, hostid1 ); + CCallbackNetBase *cnb2 = CUnifiedNetwork::getInstance()->getNetBase( *ios, hostid2 ); + if ( cnb1->hostAddress( hostid1 ).internalIPAddress() == cnb2->hostAddress( hostid2 ).internalIPAddress() )*/ + + // Implementation for NS + if (addr[0].getAddress() == getHostAddress(*ios).getAddress()) + { + grantStarting = false; + reason = toString("Service %s already found as %hu on same machine", serviceName.c_str(), ios->get()); + nlinfo(reason.c_str()); + break; + } + } + } + } + } + + if (grantStarting) + { + _OnlineServices.insert(serviceId); + } + return grantStarting; +} + +/* + * Release a service instance + */ +void CServiceInstanceManager::releaseService(NLNET::TServiceId serviceId) +{ + _OnlineServices.erase(serviceId); // not a problem if not found +} + +/* + * Display information + */ +void CServiceInstanceManager::displayInfo(NLMISC::CLog *log) const +{ + log->displayNL("Restricted services:"); + map::const_iterator ius; + for (ius = _UniqueServices.begin(); ius != _UniqueServices.end(); ++ius) + { + log->displayNL("%s -> only one per %s", (*ius).first.c_str(), (*ius).second ? "shard" : "machine"); + } + log->displayNL("Online registered services:"); + set::const_iterator ios; + for (ios = _OnlineServices.begin(); ios != _OnlineServices.end(); ++ios) + { + log->displayNL("%s", CUnifiedNetwork::getInstance()->getServiceUnifiedName(*ios).c_str()); + } +} + +/* + * Make all controlled services quit + */ +void CServiceInstanceManager::killAllServices() +{ + // Send to all known online services + std::set::const_iterator ios; + for (ios = _OnlineServices.begin(); ios != _OnlineServices.end(); ++ios) + { + doUnregisterService((TServiceId)(*ios)); + } +} diff --git a/nelns/naming_service/nelns/naming_service/service_instance_manager.h b/nelns/naming_service/nelns/naming_service/service_instance_manager.h new file mode 100644 index 0000000000..0bfaf32121 --- /dev/null +++ b/nelns/naming_service/nelns/naming_service/service_instance_manager.h @@ -0,0 +1,67 @@ +#ifndef NELNS_NAMING_SERVICE_SERVICE_INSTANCE_MANAGER_H +#define NELNS_NAMING_SERVICE_SERVICE_INSTANCE_MANAGER_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +/** + * Manager for services instances + * (Moved from the TICKS to the NS) + * Implementable with layer 5, here implemented in NS (layer 3) + * \author Olivier Cado + * \author Nevrax France + * \date 2003 + */ +class CServiceInstanceManager +{ +private: + static CServiceInstanceManager *_Instance; + +public: + static CServiceInstanceManager *getInstance(); + + /// Constructor + CServiceInstanceManager(); + + virtual ~CServiceInstanceManager(); + + /** Add the name of a service which must not be duplicated + * If uniqueOnShard is true, only one service is allowed. + * If uniqueOnShard is false, one service is allowed by physical machine. + */ + void addUniqueService(const std::string &serviceName, bool uniqueOnShard) + { + _UniqueServices.insert(std::make_pair(serviceName, uniqueOnShard)); + } + + /// Check if a service is allowed to start (if so, add it) + bool queryStartService(const std::string &serviceName, NLNET::TServiceId serviceId, const std::vector &addr, std::string &reason); + + /// Release a service instance + void releaseService(NLNET::TServiceId serviceId); + + /// Display information + void displayInfo(NLMISC::CLog *log = NLMISC::InfoLog) const; + + /// Make all controlled services quit + void killAllServices(); + +private: + /// List of restricted services + std::map _UniqueServices; + + /// List of granted (online) services + std::set _OnlineServices; +}; + +#endif // NELNS_NAMING_SERVICE_SERVICE_INSTANCE_MANAGER_H diff --git a/nelns/naming_service/nelns/naming_service/variables.cpp b/nelns/naming_service/nelns/naming_service/variables.cpp new file mode 100644 index 0000000000..a525acef21 --- /dev/null +++ b/nelns/naming_service/nelns/naming_service/variables.cpp @@ -0,0 +1,18 @@ +#include + +using std::list; + +using NLMISC::TTime; +using NLNET::CCallbackServer; +using NLNET::TServiceId; + +list RegisteredServices; /// List of all registred services + +uint16 MinBasePort = 51000; /// Ports begin at 51000 +uint16 MaxBasePort = 52000; /// (note: in this implementation there can be no more than 1000 services) + +const TServiceId BaseSId(128); /// Allocated SIds begin at 128 (except for Agent Service) + +const TTime UnregisterTimeout = 10000; /// After 10s we remove an unregister service if every server didn't ACK the message + +CCallbackServer *CallbackServer = nullptr; diff --git a/nelns/naming_service/nelns/naming_service/variables.h b/nelns/naming_service/nelns/naming_service/variables.h new file mode 100644 index 0000000000..8f3cf72602 --- /dev/null +++ b/nelns/naming_service/nelns/naming_service/variables.h @@ -0,0 +1,29 @@ +#ifndef NELNS_NAMING_SERVICE_VARIABLES_H +#define NELNS_NAMING_SERVICE_VARIABLES_H + +#include + +#include +#include + +#include +#include + +#include + +// +// Variables +// + +extern std::list RegisteredServices; /// List of all registred services + +extern uint16 MinBasePort; /// Ports begin at 51000 +extern uint16 MaxBasePort; /// (note: in this implementation there can be no more than 1000 services) + +extern const NLNET::TServiceId BaseSId; /// Allocated SIds begin at 128 (except for Agent Service) + +extern const NLMISC::TTime UnregisterTimeout; /// After 10s we remove an unregister service if every server didn't ACK the message + +extern NLNET::CCallbackServer *CallbackServer; + +#endif // NELNS_NAMING_SERVICE_VARIABLES_H diff --git a/nelns/naming_service/tests/CMakeLists.txt b/nelns/naming_service/tests/CMakeLists.txt new file mode 100644 index 0000000000..72c4c539d9 --- /dev/null +++ b/nelns/naming_service/tests/CMakeLists.txt @@ -0,0 +1,29 @@ +set(TEST_NAME "naming_service_test") + +add_executable("${TEST_NAME}" + naming_service.test.cpp +) +target_link_libraries("${TEST_NAME}" + GTest::gtest_main + GTest::gmock + nelns::ns +) +gtest_discover_tests("${TEST_NAME}" + EXTRA_ARGS "--gtest_output=xml:${CMAKE_CURRENT_BINARY_DIR}/reports/junit-${TEST_NAME}.xml" +) + + +set(TEST_NAME "service_instance_manager_test") + +add_executable("${TEST_NAME}" + service_instance_manager.test.cpp +) +target_link_libraries("${TEST_NAME}" + GTest::gtest_main + GTest::gmock + nelns::ns +) +gtest_discover_tests("${TEST_NAME}" + EXTRA_ARGS "--gtest_output=xml:${CMAKE_CURRENT_BINARY_DIR}/reports/junit-${TEST_NAME}.xml" +) + diff --git a/nelns/naming_service/tests/naming_service.test.cpp b/nelns/naming_service/tests/naming_service.test.cpp new file mode 100644 index 0000000000..b9e500fd62 --- /dev/null +++ b/nelns/naming_service/tests/naming_service.test.cpp @@ -0,0 +1,51 @@ +#include +#include + +#include + +using ::testing::Eq; +using ::testing::IsFalse; +using ::testing::IsNull; +using ::testing::IsTrue; +using ::testing::NotNull; + +TEST(CNamingService, shouldInstantiateSingleton) +{ + CNamingService instance; + + EXPECT_THAT(CNamingService::isServiceInitialized(), IsTrue()); + EXPECT_THAT(CNamingService::getInstance(), NotNull()); + EXPECT_THAT(CNamingService::getInstance(), Eq(&instance)); +} + +TEST(CNamingService, shouldNotAllowMultipleSimultaneousInstances) +{ + CNamingService first; + + ASSERT_DEATH({ CNamingService second; }, ""); +} + +TEST(CNamingService, shouldCleanupSingleton) +{ + { + CNamingService instance; + }; + + EXPECT_THAT(CNamingService::isServiceInitialized(), IsFalse()); + EXPECT_THAT(CNamingService::getInstance(), IsNull()); +} + +TEST(CNamingService, shouldAllowNewInstanceAfterDestruction) +{ + { + CNamingService first; + }; + + { + CNamingService second; + + EXPECT_THAT(CNamingService::isServiceInitialized(), IsTrue()); + EXPECT_THAT(CNamingService::getInstance(), NotNull()); + EXPECT_THAT(CNamingService::getInstance(), Eq(&second)); + }; +} diff --git a/nelns/naming_service/tests/service_instance_manager.test.cpp b/nelns/naming_service/tests/service_instance_manager.test.cpp new file mode 100644 index 0000000000..7d8cebc2ae --- /dev/null +++ b/nelns/naming_service/tests/service_instance_manager.test.cpp @@ -0,0 +1,210 @@ +#include +#include + +#include +#include + +#include + +#include +#include +#include + +using ::std::string; +using ::std::vector; + +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::IsFalse; +using ::testing::IsSupersetOf; +using ::testing::IsTrue; +using ::testing::NotNull; +using ::testing::StartsWith; +using ::testing::StrEq; + +using ::NLMISC::CLightMemDisplayer; +using ::NLMISC::CLog; + +using ::NLNET::CInetAddress; +using ::NLNET::InvalidSockId; +using ::NLNET::TServiceId; + +TEST(CServiceInstanceManager, getInstanceShouldAssertIfNotInitialized) +{ + ASSERT_DEATH({ CServiceInstanceManager::getInstance(); }, ""); +} + +TEST(CServiceInstanceManager, getInstanceShouldReturnSingleton) +{ + EXPECT_NO_THROW({ + CServiceInstanceManager instance; + + EXPECT_THAT(CServiceInstanceManager::getInstance(), NotNull()); + EXPECT_THAT(CServiceInstanceManager::getInstance(), Eq(&instance)); + }); +} + +TEST(CServiceInstanceManager, DestructorShouldCleanupSingletonInstance) +{ + { + CServiceInstanceManager instance; + } + ASSERT_DEATH({ CServiceInstanceManager::getInstance(); }, ""); +} + +TEST(CServiceInstanceManager, displayInfoShouldBeEmptyIfNothingRegistered) +{ + CServiceInstanceManager instance; + CLightMemDisplayer displayer; + CLog log = { NLMISC::CLog::LOG_ASSERT }; + log.addDisplayer(&displayer); + + instance.displayInfo(&log); + + EXPECT_THAT( + displayer.lockStrings(), + ElementsAre( + StrEq("Restricted services:\n"), + StrEq("Online registered services:\n"))); +} + +TEST(CServiceInstanceManager, addUniqueServiceShouldAddShardUniqueService) +{ + CServiceInstanceManager instance; + CLightMemDisplayer displayer; + CLog log = { NLMISC::CLog::LOG_ASSERT }; + log.addDisplayer(&displayer); + + instance.addUniqueService("unique-shard-service-name", true); + + instance.displayInfo(&log); + EXPECT_THAT( + displayer.lockStrings(), + IsSupersetOf({ StartsWith("Restricted services:"), + StartsWith("unique-shard-service-name -> only one per shard") })); +} + +TEST(CServiceInstanceManager, addUniqueServiceShouldAddMachineUniqueService) +{ + CServiceInstanceManager instance; + CLightMemDisplayer displayer; + CLog log = { NLMISC::CLog::LOG_ASSERT }; + log.addDisplayer(&displayer); + + instance.addUniqueService("unique-machine-service-name", false); + + instance.displayInfo(&log); + EXPECT_THAT( + displayer.lockStrings(), + ElementsAre( + StartsWith("Restricted services:"), + StartsWith("unique-machine-service-name -> only one per machine"), + StartsWith("Online registered services:"))); +} + +TEST(CServiceInstanceManager, queryStartServiceShouldGrantSingleService) +{ + CServiceInstanceManager instance; + TServiceId serviceId(123); + vector addresses = { "localhost:12345" }; + string reason; + + EXPECT_THAT( + instance.queryStartService("single-service", serviceId, addresses, reason), + IsTrue()); +} + +TEST(CServiceInstanceManager, queryStartServiceShouldGrantSingleShardUnqiueService) +{ + CServiceInstanceManager instance; + TServiceId serviceId(123); + vector addresses = { "localhost:12345" }; + string reason; + string serviceName = "unique-shard-service-name"; + instance.addUniqueService(serviceName, true); + + EXPECT_THAT( + instance.queryStartService(serviceName, serviceId, addresses, reason), + IsTrue()); +} + +TEST(CServiceInstanceManager, queryStartServiceShouldNotGrantAdditionalShardUnqiueService) +{ + RegisteredServices.clear(); + CServiceInstanceManager instance; + TServiceId serviceId(123); + vector addresses = { "localhost:12345" }; + string reason; + string serviceName = "unique-shard-service-name"; + RegisteredServices.emplace_back(InvalidSockId, addresses, serviceName, serviceId); + instance.addUniqueService(serviceName, true); + instance.queryStartService(serviceName, serviceId, addresses, reason); + + EXPECT_THAT( + instance.queryStartService(serviceName, serviceId, addresses, reason), + IsFalse()); + + EXPECT_THAT( + reason, + StrEq("Service unique-shard-service-name already found as 123, must be unique on shard")); +} + +TEST(CServiceInstanceManager, queryStartServiceShouldGrantAdditionalMachineUnqiueServiceOnDifferentMachine) +{ + RegisteredServices.clear(); + CServiceInstanceManager instance; + TServiceId serviceId(123); + vector firstAddresses = { "127.0.0.1:12345" }; + vector secondAddresses = { "127.0.0.2:12345" }; + string reason; + string serviceName = "unique-machine-service-name"; + RegisteredServices.emplace_back(InvalidSockId, firstAddresses, serviceName, serviceId); + instance.addUniqueService(serviceName, false); + instance.queryStartService(serviceName, serviceId, firstAddresses, reason); + + EXPECT_THAT( + instance.queryStartService(serviceName, serviceId, secondAddresses, reason), + IsTrue()); +} + +TEST(CServiceInstanceManager, queryStartServiceShouldNotGrantAdditionalMachineUnqiueServiceOnSameMachine) +{ + RegisteredServices.clear(); + CServiceInstanceManager instance; + TServiceId serviceId(123); + vector addresses = { "localhost:12345" }; + string reason; + string serviceName = "unique-machine-service-name"; + RegisteredServices.emplace_back(InvalidSockId, addresses, serviceName, serviceId); + instance.addUniqueService(serviceName, false); + instance.queryStartService(serviceName, serviceId, addresses, reason); + + EXPECT_THAT( + instance.queryStartService(serviceName, serviceId, addresses, reason), + IsFalse()); + + EXPECT_THAT( + reason, + StrEq("Service unique-machine-service-name already found as 123 on same machine")); +} + +TEST(CServiceInstanceManager, releaseServiceShouldRemoveOnlineSercie) +{ + CServiceInstanceManager instance; + TServiceId serviceId(123); + vector addresses = { "localhost:12345" }; + string reason; + CLightMemDisplayer displayer; + CLog log(NLMISC::CLog::LOG_ASSERT); + log.addDisplayer(&displayer); + instance.queryStartService("online-service", serviceId, addresses, reason); + + instance.releaseService(serviceId); + + instance.displayInfo(&log); + EXPECT_THAT( + displayer.lockStrings(), + ElementsAre( + StartsWith("Restricted services:"), + StartsWith("Online registered services:"))); +}