system: Installation progress notifications

Implement installation progress notifications as defined in
czechlight-system model
(/czechlight-system:firmware/installation/update).

The RAUC D-Bus API invokes PropertyChanged signal on its Progress [1]
property whenever the installation progresses. We capture those signals
and use them to generate Sysrepo notifications to clients.

The update contains current progress (in percents) and a message
containing current installation stage.

[1] https://rauc.readthedocs.io/en/v1.4/reference.html#gdbus-property-de-pengutronix-rauc-installer-progress

Change-Id: I310752919dde523a93943a2a1d7af79f073aedfe
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2b7db0d..a862dc3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -305,6 +305,7 @@
 
     sysrepo_fixture_env(sysrepo-czechlight-system YANG ${CMAKE_CURRENT_SOURCE_DIR}/yang/czechlight-system@2021-01-13.yang)
     velia_test(sysrepo_system-czechlightsystem velia-system DbusTesting)
+    target_link_libraries(test-sysrepo_system-czechlightsystem SysrepoTesting)
     set_tests_properties(
             test-sysrepo_system-czechlightsystem
             PROPERTIES FIXTURES_REQUIRED sysrepo:env:sysrepo-czechlight-system
diff --git a/src/system/CzechlightSystem.cpp b/src/system/CzechlightSystem.cpp
index 49479f2..4256015 100644
--- a/src/system/CzechlightSystem.cpp
+++ b/src/system/CzechlightSystem.cpp
@@ -35,7 +35,18 @@
                   utils::valuesPush(data, std::make_shared<::sysrepo::Session>(m_srConn, SR_DS_OPERATIONAL));
               }
           },
-          []([[maybe_unused]] int32_t perc, [[maybe_unused]] const std::string& msg) { /* TODO notifications */ },
+          [this](int32_t perc, const std::string& msg) {
+              std::map<std::string, std::string> data = {
+                  {CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/update/message", msg},
+                  {CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/update/progress", std::to_string(perc)},
+              };
+
+              libyang::S_Data_Node dataNode;
+              auto session = std::make_shared<::sysrepo::Session>(m_srConn);
+
+              utils::valuesToYang(data, session, dataNode);
+              session->event_notif_send(dataNode);
+          },
           [this](int32_t retVal, const std::string& lastError) {
               std::map<std::string, std::string> data {
                   {CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/message", lastError},
diff --git a/tests/sysrepo_system-czechlightsystem.cpp b/tests/sysrepo_system-czechlightsystem.cpp
index 07dc575..fe0f2a2 100644
--- a/tests/sysrepo_system-czechlightsystem.cpp
+++ b/tests/sysrepo_system-czechlightsystem.cpp
@@ -5,6 +5,7 @@
 #include "test_log_setup.h"
 #include "test_sysrepo_helpers.h"
 #include "tests/configure.cmake.h"
+#include "tests/mock/sysrepo/events.h"
 
 using namespace std::literals;
 
@@ -109,6 +110,12 @@
                 {"/installation/message", ""},
                 {"/installation/status", "in-progress"},
             };
+            size_t expectedNotificationsCount;
+            std::string expectedLastNotificationMsg;
+
+            // subscribe to notifications
+            EventWatcher events;
+            subscription->event_notif_subscribe("czechlight-system", events, "/czechlight-system:firmware/installation/update");
 
             SECTION("Successfull install")
             {
@@ -118,6 +125,8 @@
                     {"/installation/message", ""},
                     {"/installation/status", "succeeded"},
                 };
+                expectedNotificationsCount = 22;
+                expectedLastNotificationMsg = "Installing done.";
             }
 
             SECTION("Unsuccessfull install")
@@ -128,6 +137,8 @@
                     {"/installation/message", "Failed to download bundle https://10.88.3.11:8000/update.raucb: Transfer failed: error:1408F10B:SSL routines:ssl3_get_record:wrong version number"},
                     {"/installation/status", "failed"},
                 };
+                expectedNotificationsCount = 6;
+                expectedLastNotificationMsg = "Installing failed.";
             }
 
             raucServer.installBundleBehaviour(installType);
@@ -139,6 +150,20 @@
 
             std::this_thread::sleep_for(2s); // lets wait a while, so the installation can finish
             REQUIRE(dataFromSysrepo(client, "/czechlight-system:firmware", SR_DS_OPERATIONAL) == expectedFinished);
+
+            // check updates notification count and that at least some of them are reasonable
+            REQUIRE(events.count() == expectedNotificationsCount);
+            REQUIRE(events.peek(0).data["/czechlight-system:firmware/installation/update/message"] == "Installing");
+            REQUIRE(events.peek(0).data["/czechlight-system:firmware/installation/update/progress"] == "0");
+            REQUIRE(events.peek(events.count() - 1).data["/czechlight-system:firmware/installation/update/message"] == expectedLastNotificationMsg);
+            REQUIRE(events.peek(events.count() - 1).data["/czechlight-system:firmware/installation/update/progress"] == "100");
+
+            // check updates notification progress is an increasing sequence
+            for (size_t i = 1; i < events.count(); i++) {
+                auto prevProgress = std::stoi(events.peek(i - 1).data["/czechlight-system:firmware/installation/update/progress"]);
+                auto currProgress = std::stoi(events.peek(i).data["/czechlight-system:firmware/installation/update/progress"]);
+                REQUIRE(prevProgress <= currProgress);
+            }
         }
 
         SECTION("Invoke another installation before the first finishes")
diff --git a/tests/test_sysrepo_helpers.h b/tests/test_sysrepo_helpers.h
index bf8bf5f..54d2d71 100644
--- a/tests/test_sysrepo_helpers.h
+++ b/tests/test_sysrepo_helpers.h
@@ -44,6 +44,7 @@
     auto srSess = std::make_shared<sysrepo::Session>(srConn); \
     auto srSubs = std::make_shared<sysrepo::Subscribe>(srSess);
 
-#define TEST_SYSREPO_INIT_CLIENT                               \
-    auto clientConn = std::make_shared<sysrepo::Connection>(); \
-    auto client = std::make_shared<sysrepo::Session>(clientConn);
+#define TEST_SYSREPO_INIT_CLIENT                                  \
+    auto clientConn = std::make_shared<sysrepo::Connection>();    \
+    auto client = std::make_shared<sysrepo::Session>(clientConn); \
+    auto subscription = std::make_shared<sysrepo::Subscribe>(client);