Add synchronous SDL readiness waiting API 93/6593/4 1.4.0
authorTimo Tietavainen <timo.tietavainen@nokia.com>
Wed, 11 Aug 2021 04:33:30 +0000 (07:33 +0300)
committerTimo Tietavainen <timo.tietavainen@nokia.com>
Wed, 11 Aug 2021 18:02:03 +0000 (21:02 +0300)
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 <timo.tietavainen@nokia.com>
Change-Id: Ic334cf04fa6c80f04a3c3e06b01fbf1ff7e940fd

12 files changed:
README.md
configure.ac
debian/changelog.in
docs/release-notes.rst
docs/user-guide.rst
include/private/syncstorageimpl.hpp
include/sdl/syncstorage.hpp
include/sdl/tst/mockablesyncstorage.hpp
rpm/sdl.spec.in
src/cli/testgetsetcommand.cpp
src/syncstorageimpl.cpp
tst/syncstorageimpl_test.cpp

index 0b88683..a8f9a35 100644 (file)
--- 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.
 
index 9be6d77..514c403 100644 (file)
@@ -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])
index 45e9059..16fe8b8 100644 (file)
@@ -1,3 +1,9 @@
+sdl (1.4.0-1) UNRELEASED; urgency=low
+
+  * Add synchronous readiness check API
+
+ -- Timo Tietavainen <timo.tietavainen@nokia.com>  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
index 4e00c74..0f0d870 100644 (file)
@@ -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
index 9b5d6a8..6024f93 100644 (file)
@@ -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.
 
index 735274f..b0f5fb5 100644 (file)
@@ -39,6 +39,8 @@ namespace shareddatalayer
         SyncStorageImpl(std::unique_ptr<AsyncStorage> 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);
index 409207f..29f1f9a 100644 (file)
@@ -75,6 +75,43 @@ namespace shareddatalayer
 
         using DataMap = std::map<Key, Data>;
 
+        /**
+         * 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.
index cde4adc..4bb87af 100644 (file)
@@ -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__); }
index f80ea76..c63909b 100755 (executable)
@@ -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 <timo.tietavainen@nokia.com> - 1.4.0-1
+- Add synchronous readiness check API
+
 * Thu Aug 05 2021 Timo Tietavainen <timo.tietavainen@nokia.com> - 1.3.0-1
 - Definable timeout for DB backend readiness in synchronous SDL API
 - Fix SDL configuration file path Valgrind errors
index 007cb95..fda0cd4 100644 (file)
@@ -16,11 +16,13 @@ using namespace shareddatalayer::cli;
 
 namespace
 {
-    std::shared_ptr<shareddatalayer::SyncStorage> createSyncStorage(std::ostream& out)
+    std::shared_ptr<shareddatalayer::SyncStorage> 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<int>());
         auto ns(map["ns"].as<std::string>());
         setTimeout(timeout);
-        auto sdl(createSyncStorage(out));
+        auto sdl(createSyncStorage(ns, out));
         if (sdl == nullptr)
             return EXIT_FAILURE;
 
index 76c1546..3eaeb03 100644 (file)
@@ -79,9 +79,10 @@ SyncStorageImpl::SyncStorageImpl(std::unique_ptr<AsyncStorage> 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<std::chrono::milliseconds>(operationTimeout).count() / 10;
+        auto timeout_ms(std::chrono::duration_cast<std::chrono::milliseconds>(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)
index 7860334..570b61a 100644 (file)
@@ -30,6 +30,7 @@
 #include <sdl/notconnected.hpp>
 #include <sdl/operationinterrupted.hpp>
 #include <sdl/rejectedbybackend.hpp>
+#include <sdl/rejectedbysdl.hpp>
 
 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<AsyncStorageMock>()),
@@ -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<std::chrono::milliseconds>(TEST_READY_WAIT_TIMEOUT).count() / 10),
             TEST_OPERATION_POLL_WAIT_TIMEOUT(std::chrono::duration_cast<std::chrono::milliseconds>(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;