From d565df6613d97c08664a00f3fbc7cb5fdcf233f6 Mon Sep 17 00:00:00 2001 From: Timo Tietavainen Date: Wed, 11 Aug 2021 07:33:30 +0300 Subject: [PATCH] Add synchronous SDL readiness waiting API Add a new synchronous API function 'SyncStorage::waitReady()', which can be used to wait SDL service readiness for the given namespace. Function can be used to validate SDL service readiness for example after application container start-up before any other SDL operations (set, get, remove) are tried to do. Also timeout value can be given to 'SyncStorage::waitReady()' function to define expiration time for the case when readiness validation hangs too long. Issue-ID: RIC-226 Signed-off-by: Timo Tietavainen Change-Id: Ic334cf04fa6c80f04a3c3e06b01fbf1ff7e940fd --- README.md | 4 ++-- configure.ac | 6 ++--- debian/changelog.in | 6 +++++ docs/release-notes.rst | 4 ++++ docs/user-guide.rst | 5 ++++ include/private/syncstorageimpl.hpp | 6 ++++- include/sdl/syncstorage.hpp | 37 +++++++++++++++++++++++++++++ include/sdl/tst/mockablesyncstorage.hpp | 2 ++ rpm/sdl.spec.in | 5 +++- src/cli/testgetsetcommand.cpp | 6 +++-- src/syncstorageimpl.cpp | 29 +++++++++++++++++------ tst/syncstorageimpl_test.cpp | 41 ++++++++++++++++++++++++++++++++- 12 files changed, 134 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 0b88683..a8f9a35 100644 --- a/README.md +++ b/README.md @@ -203,8 +203,8 @@ a directory, what was defined by '--with-gcov-report-dir' configure option. In addition, graphical gcov front-ends such as lcov can be used for coverage analysis: - lcov --directory tst/ --directory src --capture --output-file coverage.info - genhtml coverage.info --output-directory out + lcov --directory tst/ --directory src --capture --output-file coverage.info + genhtml coverage.info --output-directory out Open the out/index.html using any web browser. diff --git a/configure.ac b/configure.ac index 9be6d77..514c403 100644 --- a/configure.ac +++ b/configure.ac @@ -10,7 +10,7 @@ # Change the numbers just before release. m4_define([SDL_MAJOR], [1]) -m4_define([SDL_MINOR], [3]) +m4_define([SDL_MINOR], [4]) m4_define([SDL_MICRO], [0]) # SDL ABI version with libtool @@ -27,8 +27,8 @@ m4_define([SDL_MICRO], [0]) # # Change the numbers just before release. -m4_define([SDL_CURRENT], [3]) -m4_define([SDL_REVISION], [10]) +m4_define([SDL_CURRENT], [4]) +m4_define([SDL_REVISION], [11]) m4_define([SDL_AGE], [0]) AC_INIT([shareddatalayer], [SDL_MAJOR.SDL_MINOR.SDL_MICRO], [], [], [https://gerrit.o-ran-sc.org/r/admin/repos/ric-plt/sdl]) diff --git a/debian/changelog.in b/debian/changelog.in index 45e9059..16fe8b8 100644 --- a/debian/changelog.in +++ b/debian/changelog.in @@ -1,3 +1,9 @@ +sdl (1.4.0-1) UNRELEASED; urgency=low + + * Add synchronous readiness check API + + -- Timo Tietavainen Wed, 11 Aug 2021 08:02:59 +0300 + sdl (1.3.0-1) UNRELEASED; urgency=low * Definable timeout for DB backend readiness in synchronous SDL API diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 4e00c74..0f0d870 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -29,6 +29,10 @@ This document provides the release notes of the sdl library. Version history --------------- +[1.4.0] - 2021-08-11 + +* Add synchronous readiness check API + [1.3.0] - 2021-08-05 * Definable timeout for DB backend readiness in synchronous SDL API diff --git a/docs/user-guide.rst b/docs/user-guide.rst index 9b5d6a8..6024f93 100644 --- a/docs/user-guide.rst +++ b/docs/user-guide.rst @@ -498,6 +498,11 @@ Using SDL APIs operations. See waitReadyAsync() function `doxygen documentation <#doxygen-generated-sdl-api-documentation>`_ for corresponding asynchronous API for details. +* Use waitReady() function before doing first operation via synchronous + APIs to ensure that SDL and backend data storage are ready to handle + operations. See waitReady() function + `doxygen documentation <#doxygen-generated-sdl-api-documentation>`_ + for corresponding synchronous API for details. * Avoid using heavy search functions (for example: *AsyncStorage::findKeys()*). Rather define your keys so that you know which keys should be read. diff --git a/include/private/syncstorageimpl.hpp b/include/private/syncstorageimpl.hpp index 735274f..b0f5fb5 100644 --- a/include/private/syncstorageimpl.hpp +++ b/include/private/syncstorageimpl.hpp @@ -39,6 +39,8 @@ namespace shareddatalayer SyncStorageImpl(std::unique_ptr asyncStorage, System& system); + virtual void waitReady(const Namespace& ns, const std::chrono::steady_clock::duration& timeout) override; + virtual void set(const Namespace& ns, const DataMap& dataMap) override; virtual bool setIf(const Namespace& ns, const Key& key, const Data& oldData, const Data& newData) override; @@ -75,12 +77,14 @@ namespace shareddatalayer void pollAndHandleEvents(int timeout_ms); - void waitForReadinessCheckCallback(); + void waitForReadinessCheckCallback(const std::chrono::steady_clock::duration& timeout); void waitForOperationCallback(); void waitSdlToBeReady(const Namespace& ns); + void waitSdlToBeReady(const Namespace& ns, const std::chrono::steady_clock::duration& timeout); + void waitReadyAck(const std::error_code& error); void modifyAck(const std::error_code& error); diff --git a/include/sdl/syncstorage.hpp b/include/sdl/syncstorage.hpp index 409207f..29f1f9a 100644 --- a/include/sdl/syncstorage.hpp +++ b/include/sdl/syncstorage.hpp @@ -75,6 +75,43 @@ namespace shareddatalayer using DataMap = std::map; + /** + * Wait for the service to become ready to serve. There is typically a waiting period + * when product cluster is brought up (deploying container orchestrated service, + * container restart etc.). The function can be called and used to synchronize the + * application startup with the readiness of the underlying data storage. + * + * This function can also be used if SDL returns an error indicating connection cut + * to backend data storage. In that situation client can use this function to get an + * indication when SDL is again ready to serve. SDL error code documentation indicates + * the error codes for which the usage of this function is applicable. + * + * Exceptions thrown (excluding standard exceptions such as std::bad_alloc) are all + * derived from shareddatalayer::Exception base class. Client can catch only that + * exception if separate handling for different shareddatalayer error situations is not + * needed. + * + * @param ns Namespace under which this operation is targeted. As it is possible to + * use different DB backend instances for namespaces, it is necessary to + * provide namespace for this call to test the DB backend readiness of that + * particular namespace. it is recommended to call this always when starting + * to use new namespace. + * + * @param timeout Timeout value after which readiness waiting will be expired in case + * the SDL service won't come up. It is recommended to use rather long + * timeout value to have enough time for a system to recover for example + * from restart scenarios. Suitable timeout value depends greatly from + * the environment itself. As an example an application could use 10 + * seconds timeout value and have a loop what does a re-try every time + * when previous waitReady call has failed. On a side note, timeout + * value 0 means that readiness is waited interminable. + * + * @throw NotConnected if shareddatalayer is not connected to the backend data storage. + * @throw RejectedBySdl if SDL rejects the request. + * @throw BackendError if the backend data storage fails to process the request. + */ + virtual void waitReady(const Namespace& ns, const std::chrono::steady_clock::duration& timeout) = 0; + /** * Write data to shared data layer storage. Writing is done atomically, i.e. either * all succeeds or all fails. diff --git a/include/sdl/tst/mockablesyncstorage.hpp b/include/sdl/tst/mockablesyncstorage.hpp index cde4adc..4bb87af 100644 --- a/include/sdl/tst/mockablesyncstorage.hpp +++ b/include/sdl/tst/mockablesyncstorage.hpp @@ -46,6 +46,8 @@ namespace shareddatalayer public: MockableSyncStorage() { } + virtual void waitReady(const Namespace&, const std::chrono::steady_clock::duration&) override { logAndAbort(__PRETTY_FUNCTION__); } + virtual void set(const Namespace&, const DataMap&) override { logAndAbort(__PRETTY_FUNCTION__); } virtual bool setIf(const Namespace&, const Key&, const Data&, const Data&) override { logAndAbort(__PRETTY_FUNCTION__); } diff --git a/rpm/sdl.spec.in b/rpm/sdl.spec.in index f80ea76..c63909b 100755 --- a/rpm/sdl.spec.in +++ b/rpm/sdl.spec.in @@ -1,5 +1,5 @@ Name: sdl -Version: 1.3.0 +Version: 1.4.0 Release: 1%{?dist} Summary: C++ API library for Shared Data Layer clients @@ -50,6 +50,9 @@ rm -f %{buildroot}%{_libdir}/lib*.*a %{_includedir}/sdl %changelog +* Thu Aug 11 2021 Timo Tietavainen - 1.4.0-1 +- Add synchronous readiness check API + * Thu Aug 05 2021 Timo Tietavainen - 1.3.0-1 - Definable timeout for DB backend readiness in synchronous SDL API - Fix SDL configuration file path Valgrind errors diff --git a/src/cli/testgetsetcommand.cpp b/src/cli/testgetsetcommand.cpp index 007cb95..fda0cd4 100644 --- a/src/cli/testgetsetcommand.cpp +++ b/src/cli/testgetsetcommand.cpp @@ -16,11 +16,13 @@ using namespace shareddatalayer::cli; namespace { - std::shared_ptr createSyncStorage(std::ostream& out) + std::shared_ptr createSyncStorage(const std::string& nsStr, std::ostream& out) { try { auto sdl(shareddatalayer::SyncStorage::create()); + sdl->waitReady(nsStr, std::chrono::minutes(1)); + sdl->setOperationTimeout(std::chrono::seconds(5)); return sdl; } catch (const shareddatalayer::Exception& error) @@ -95,7 +97,7 @@ namespace const auto timeout(map["timeout"].as()); auto ns(map["ns"].as()); setTimeout(timeout); - auto sdl(createSyncStorage(out)); + auto sdl(createSyncStorage(ns, out)); if (sdl == nullptr) return EXIT_FAILURE; diff --git a/src/syncstorageimpl.cpp b/src/syncstorageimpl.cpp index 76c1546..3eaeb03 100644 --- a/src/syncstorageimpl.cpp +++ b/src/syncstorageimpl.cpp @@ -79,9 +79,10 @@ SyncStorageImpl::SyncStorageImpl(std::unique_ptr pAsyncStorage, { } -void SyncStorageImpl::waitReadyAck(const std::error_code&) +void SyncStorageImpl::waitReadyAck(const std::error_code& error) { isReady = true; + localError = error; } void SyncStorageImpl::modifyAck(const std::error_code& error) @@ -129,30 +130,44 @@ void SyncStorageImpl::pollAndHandleEvents(int timeout_ms) asyncStorage->handleEvents(); } -void SyncStorageImpl::waitForReadinessCheckCallback() +void SyncStorageImpl::waitForReadinessCheckCallback(const std::chrono::steady_clock::duration& timeout) { - if (operationTimeout == std::chrono::steady_clock::duration::zero()) + if (timeout == std::chrono::steady_clock::duration::zero()) { while (!isReady) pollAndHandleEvents(NO_TIMEOUT); } else { - int pollTimeout_ms = std::chrono::duration_cast(operationTimeout).count() / 10; + auto timeout_ms(std::chrono::duration_cast(timeout).count()); + auto pollTimeout_ms(timeout_ms / 10); std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); - while(!isReady && (std::chrono::steady_clock::now() - start < operationTimeout)) + while(!isReady && (std::chrono::steady_clock::now() - start < std::chrono::milliseconds(timeout_ms))) pollAndHandleEvents(pollTimeout_ms); } } void SyncStorageImpl::waitSdlToBeReady(const Namespace& ns) +{ + waitSdlToBeReady(ns, operationTimeout); +} + +void SyncStorageImpl::waitSdlToBeReady(const Namespace& ns, const std::chrono::steady_clock::duration& timeout) { isReady = false; asyncStorage->waitReadyAsync(ns, std::bind(&shareddatalayer::SyncStorageImpl::waitReadyAck, this, - std::error_code())); - waitForReadinessCheckCallback(); + std::placeholders::_1)); + waitForReadinessCheckCallback(timeout); +} + +void SyncStorageImpl::waitReady(const Namespace& ns, const std::chrono::steady_clock::duration& timeout) +{ + waitSdlToBeReady(ns, timeout); + if(!isReady) + throw RejectedBySdl("Timeout, SDL service not ready for the '" + ns + "' namespace"); + verifyBackendResponse(); } void SyncStorageImpl::set(const Namespace& ns, const DataMap& dataMap) diff --git a/tst/syncstorageimpl_test.cpp b/tst/syncstorageimpl_test.cpp index 7860334..570b61a 100644 --- a/tst/syncstorageimpl_test.cpp +++ b/tst/syncstorageimpl_test.cpp @@ -30,6 +30,7 @@ #include #include #include +#include using namespace shareddatalayer; using namespace shareddatalayer::redis; @@ -59,7 +60,9 @@ namespace SyncStorage::DataMap dataMap; SyncStorage::Keys keys; const SyncStorage::Namespace ns; + std::chrono::steady_clock::duration TEST_READY_WAIT_TIMEOUT; std::chrono::steady_clock::duration TEST_OPERATION_WAIT_TIMEOUT; + int TEST_READY_POLL_WAIT_TIMEOUT; int TEST_OPERATION_POLL_WAIT_TIMEOUT; SyncStorageImplTest(): asyncStorageMockPassedToImplementation(new StrictMock()), @@ -68,7 +71,9 @@ namespace dataMap({{ "key1", { 0x0a, 0x0b, 0x0c } }, { "key2", { 0x0d, 0x0e, 0x0f, 0xff } }}), keys({ "key1", "key2" }), ns("someKnownNamespace"), + TEST_READY_WAIT_TIMEOUT(std::chrono::minutes(1)), TEST_OPERATION_WAIT_TIMEOUT(std::chrono::seconds(1)), + TEST_READY_POLL_WAIT_TIMEOUT(std::chrono::duration_cast(TEST_READY_WAIT_TIMEOUT).count() / 10), TEST_OPERATION_POLL_WAIT_TIMEOUT(std::chrono::duration_cast(TEST_OPERATION_WAIT_TIMEOUT).count() / 10) { expectConstructorCalls(); @@ -156,6 +161,16 @@ namespace })); } + void expectHandleEvents_callWaitReadyAckWithError() + { + EXPECT_CALL(*asyncStorageMockRawPtr, handleEvents()) + .Times(1) + .WillOnce(Invoke([this]() + { + savedReadyAck(AsyncRedisCommandDispatcherErrorCode::NOT_CONNECTED); + })); + } + void expectHandleEvents_callModifyAck() { EXPECT_CALL(*asyncStorageMockRawPtr, handleEvents()) @@ -173,7 +188,6 @@ namespace .WillOnce(SaveArg<1>(&savedReadyAck)); } - void expectModifyAckWithError() { EXPECT_CALL(*asyncStorageMockRawPtr, handleEvents()) @@ -327,6 +341,31 @@ TEST_F(SyncStorageImplTest, EventsAreNotHandledWhenThereIsAnExceptionalCondition syncStorage->set(ns, dataMap); } +TEST_F(SyncStorageImplTest, WaitReadySuccessfully) +{ + InSequence dummy; + expectWaitReadyAsync(); + expectPollWait(TEST_READY_POLL_WAIT_TIMEOUT); + expectHandleEvents_callWaitReadyAck(); + syncStorage->waitReady(ns, TEST_READY_WAIT_TIMEOUT); +} + +TEST_F(SyncStorageImplTest, WaitReadyCanThrowRejectedBySdl) +{ + InSequence dummy; + expectWaitReadyAsync(); + EXPECT_THROW(syncStorage->waitReady(ns, std::chrono::nanoseconds(1)), RejectedBySdl); +} + +TEST_F(SyncStorageImplTest, WaitReadyCanThrowNotConnected) +{ + InSequence dummy; + expectWaitReadyAsync(); + expectPollWait(TEST_READY_POLL_WAIT_TIMEOUT); + expectHandleEvents_callWaitReadyAckWithError(); + EXPECT_THROW(syncStorage->waitReady(ns, TEST_READY_WAIT_TIMEOUT), NotConnected); +} + TEST_F(SyncStorageImplTest, SetSuccessfully) { InSequence dummy; -- 2.16.6