Merge "docs: this is now more than just a health monitor"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 76a79ae..720aa6b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,8 +39,8 @@
message(FATAL_ERROR "Systemd 245 has a bug affecting DBus method calls using sdbus-c++. Please see https://github.com/Kistler-Group/sdbus-cpp/issues/106.")
endif()
-pkg_check_modules(SYSREPO REQUIRED sysrepo-cpp>=1.4.100 IMPORTED_TARGET sysrepo)
-pkg_check_modules(LIBYANG REQUIRED libyang-cpp>=1.0.209 IMPORTED_TARGET libyang)
+pkg_check_modules(SYSREPO REQUIRED sysrepo-cpp>=1.4.103 IMPORTED_TARGET sysrepo)
+pkg_check_modules(LIBYANG REQUIRED libyang-cpp>=1.0.215 IMPORTED_TARGET libyang)
include(GNUInstallDirs)
@@ -55,6 +55,9 @@
yang/ietf-netconf-acm@2018-02-14.yang
yang/ietf-system@2014-08-06.yang
yang/ietf-yang-types@2013-07-15.yang
+ yang/ietf-ethertypes@2019-03-04.yang
+ yang/ietf-interfaces@2018-02-20.yang
+ yang/ietf-packet-fields@2019-03-04.yang
yang/ietf-access-control-list@2019-03-04.yang
yang/czechlight-firewall@2021-01-25.yang
)
@@ -131,6 +134,8 @@
add_library(velia-system STATIC
src/system/RAUC.cpp
src/system/RAUC.h
+ src/system/CzechlightSystem.cpp
+ src/system/CzechlightSystem.h
src/system/IETFSystem.cpp
src/system/IETFSystem.h
)
@@ -156,20 +161,18 @@
# - daemons
-add_executable(veliad
- src/main.cpp
- src/Factory.cpp
- src/Factory.h
- )
-target_link_libraries(veliad
- PUBLIC
+add_executable(veliad-health
+ src/main-health.cpp
+ src/health/Factory.cpp
+ src/health/Factory.h
+ )
+target_link_libraries(veliad-health
+ PUBLIC
velia-health
- velia-ietf-hardware
- velia-ietf-hardware-sysrepo
docopt
- )
-add_dependencies(veliad target-VELIA_VERSION)
-target_include_directories(veliad PUBLIC ${CMAKE_BINARY_DIR})
+ )
+add_dependencies(veliad-health target-VELIA_VERSION)
+target_include_directories(veliad-health PUBLIC ${CMAKE_BINARY_DIR})
add_executable(veliad-system
src/main-system.cpp
@@ -195,6 +198,20 @@
add_dependencies(veliad-firewall target-VELIA_VERSION)
target_include_directories(veliad-firewall PUBLIC ${CMAKE_BINARY_DIR})
+add_executable(veliad-hardware
+ src/main-hardware.cpp
+ src/ietf-hardware/Factory.cpp
+ src/ietf-hardware/Factory.h
+ )
+target_link_libraries(veliad-hardware
+ PUBLIC
+ velia-ietf-hardware
+ velia-ietf-hardware-sysrepo
+ docopt
+ )
+add_dependencies(veliad-hardware target-VELIA_VERSION)
+target_include_directories(veliad-hardware PUBLIC ${CMAKE_BINARY_DIR})
+
# Testing
include(CTest)
if(BUILD_TESTING)
@@ -212,6 +229,12 @@
target_include_directories(DoctestIntegration PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/tests/)
target_compile_definitions(DoctestIntegration PUBLIC DOCTEST_CONFIG_SUPER_FAST_ASSERTS)
+ add_library(SysrepoTesting STATIC
+ tests/mock/sysrepo/events.cpp
+ tests/mock/sysrepo/events.h
+ )
+ target_link_libraries(SysrepoTesting DoctestIntegration PkgConfig::SYSREPO)
+
add_library(DbusTesting STATIC
tests/dbus-helpers/dbus_rauc_server.cpp
tests/dbus-helpers/dbus_rauc_server.h
@@ -279,6 +302,10 @@
velia_test(health_output-led velia-health)
velia_test(system_rauc velia-system DbusTesting)
+ set_tests_properties(
+ test-system_rauc
+ PROPERTIES RESOURCE_LOCK dbus-rauc
+ )
velia_test(hardware_emmc velia-ietf-hardware FsTestUtils)
velia_test(hardware_hwmon velia-ietf-hardware FsTestUtils)
@@ -289,6 +316,15 @@
test-sysrepo_system-ietfsystem
PROPERTIES FIXTURES_REQUIRED sysrepo:env:sysrepo-ietf-system
RESOURCE_LOCK sysrepo
+)
+
+ 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
+ RESOURCE_LOCK "sysrepo;dbus-rauc"
)
sysrepo_fixture_env(sysrepo-ietf-hardware YANG ${CMAKE_CURRENT_SOURCE_DIR}/yang/iana-hardware@2018-03-13.yang YANG ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-hardware@2018-03-13.yang FEATURE hardware-sensor)
@@ -365,5 +401,7 @@
set(YANG_DIR ${CMAKE_INSTALL_PREFIX}/share/velia/yang)
install(FILES ${YANG_SRCS} DESTINATION ${YANG_DIR})
-install(TARGETS veliad RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/)
+install(TARGETS veliad-health RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/)
install(TARGETS veliad-system RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/)
+install(TARGETS veliad-hardware RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/)
+install(TARGETS veliad-firewall RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/)
diff --git a/ci/build.sh b/ci/build.sh
index 5af1e6a..bfd3457 100755
--- a/ci/build.sh
+++ b/ci/build.sh
@@ -31,6 +31,7 @@
export CFLAGS="-fsanitize=undefined ${CFLAGS}"
export CXXFLAGS="-fsanitize=undefined ${CXXFLAGS}"
export LDFLAGS="-fsanitize=undefined ${LDFLAGS}"
+ export UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1
fi
if [[ $ZUUL_JOB_NAME =~ .*-asan ]]; then
@@ -43,7 +44,7 @@
export CFLAGS="-fsanitize=thread ${CFLAGS}"
export CXXFLAGS="-fsanitize=thread ${CXXFLAGS}"
export LDFLAGS="-fsanitize=thread ${LDFLAGS}"
- export TSAN_OPTIONS="suppressions=$HOME/target/tsan.supp:detect_deadlocks=0"
+ export TSAN_OPTIONS="suppressions=$HOME/target/tsan.supp"
fi
if [[ $ZUUL_JOB_NAME =~ .*-cover.* ]]; then
diff --git a/src/Factory.h b/src/Factory.h
deleted file mode 100644
index 521bc4f..0000000
--- a/src/Factory.h
+++ /dev/null
@@ -1,16 +0,0 @@
-#pragma once
-#include <memory>
-#include "health/State.h"
-#include "health/outputs/SlotWrapper.h"
-
-namespace velia::ietf_hardware {
-class IETFHardware;
-}
-
-namespace velia::ietf_hardware {
-std::shared_ptr<ietf_hardware::IETFHardware> create(const std::string& applianceName);
-}
-
-namespace velia::health {
-boost::signals2::SlotWrapper<void, health::State> createOutput(const std::string& applianceName);
-}
diff --git a/src/health/Factory.cpp b/src/health/Factory.cpp
new file mode 100644
index 0000000..607a985
--- /dev/null
+++ b/src/health/Factory.cpp
@@ -0,0 +1,18 @@
+#include "Factory.h"
+#include "health/outputs/LedSysfsDriver.h"
+#include "health/outputs/SlotWrapper.h"
+#include "health/outputs/callables.h"
+
+namespace velia::health {
+boost::signals2::SlotWrapper<void, health::State> createOutput(const std::string& applianceName)
+{
+ if (applianceName == "czechlight-clearfog") {
+ return boost::signals2::SlotWrapper<void, State>(std::make_shared<LedOutputCallback>(
+ std::make_shared<LedSysfsDriver>("/sys/class/leds/status:red/"),
+ std::make_shared<LedSysfsDriver>("/sys/class/leds/status:green/"),
+ std::make_shared<LedSysfsDriver>("/sys/class/leds/status:blue/")));
+ } else {
+ throw std::runtime_error("Unknown appliance '" + applianceName + "'");
+ }
+}
+}
diff --git a/src/health/Factory.h b/src/health/Factory.h
new file mode 100644
index 0000000..c4207a2
--- /dev/null
+++ b/src/health/Factory.h
@@ -0,0 +1,8 @@
+#pragma once
+#include <memory>
+#include "health/State.h"
+#include "health/outputs/SlotWrapper.h"
+
+namespace velia::health {
+boost::signals2::SlotWrapper<void, health::State> createOutput(const std::string& applianceName);
+}
diff --git a/src/Factory.cpp b/src/ietf-hardware/Factory.cpp
similarity index 79%
rename from src/Factory.cpp
rename to src/ietf-hardware/Factory.cpp
index 408e06d..c214c85 100644
--- a/src/Factory.cpp
+++ b/src/ietf-hardware/Factory.cpp
@@ -1,7 +1,4 @@
#include "Factory.h"
-#include "health/outputs/LedSysfsDriver.h"
-#include "health/outputs/SlotWrapper.h"
-#include "health/outputs/callables.h"
#include "ietf-hardware/IETFHardware.h"
#include "ietf-hardware/sysfs/EMMC.h"
#include "ietf-hardware/sysfs/HWMon.h"
@@ -41,17 +38,3 @@
}
}
-
-namespace velia::health {
-boost::signals2::SlotWrapper<void, health::State> createOutput(const std::string& applianceName)
-{
- if (applianceName == "czechlight-clearfog") {
- return boost::signals2::SlotWrapper<void, State>(std::make_shared<LedOutputCallback>(
- std::make_shared<LedSysfsDriver>("/sys/class/leds/status:red/"),
- std::make_shared<LedSysfsDriver>("/sys/class/leds/status:green/"),
- std::make_shared<LedSysfsDriver>("/sys/class/leds/status:blue/")));
- } else {
- throw std::runtime_error("Unknown appliance '" + applianceName + "'");
- }
-}
-}
diff --git a/src/ietf-hardware/Factory.h b/src/ietf-hardware/Factory.h
new file mode 100644
index 0000000..fcd83f5
--- /dev/null
+++ b/src/ietf-hardware/Factory.h
@@ -0,0 +1,10 @@
+#pragma once
+#include <memory>
+
+namespace velia::ietf_hardware {
+class IETFHardware;
+}
+
+namespace velia::ietf_hardware {
+std::shared_ptr<ietf_hardware::IETFHardware> create(const std::string& applianceName);
+}
diff --git a/src/main-firewall.cpp b/src/main-firewall.cpp
index aca2184..db6b33d 100644
--- a/src/main-firewall.cpp
+++ b/src/main-firewall.cpp
@@ -54,7 +54,7 @@
loggingSink = std::make_shared<spdlog::sinks::ansicolor_stderr_sink_mt>();
}
- auto args = docopt::docopt(usage, {argv + 1, argv + argc}, true, "veliad " VELIA_VERSION, true);
+ auto args = docopt::docopt(usage, {argv + 1, argv + argc}, true, "veliad-firewall " VELIA_VERSION, true);
velia::utils::initLogs(loggingSink);
spdlog::set_level(spdlog::level::info);
diff --git a/src/main.cpp b/src/main-hardware.cpp
similarity index 66%
copy from src/main.cpp
copy to src/main-hardware.cpp
index cc8f0e6..7b0bd2e 100644
--- a/src/main.cpp
+++ b/src/main-hardware.cpp
@@ -1,19 +1,15 @@
#include <docopt.h>
-#include <future>
-#include <sdbus-c++/sdbus-c++.h>
#include <spdlog/sinks/ansicolor_sink.h>
#include <spdlog/spdlog.h>
#include <sysrepo-cpp/Session.hpp>
-#include "Factory.h"
#include "VELIA_VERSION.h"
-#include "health/inputs/DbusSystemdInput.h"
-#include "health/manager/StateManager.h"
-#include "health/outputs/callables.h"
+#include "ietf-hardware/Factory.h"
+#include "ietf-hardware/IETFHardware.h"
#include "ietf-hardware/sysrepo/Sysrepo.h"
-#include "main.h"
#include "utils/exceptions.h"
#include "utils/journal.h"
#include "utils/log-init.h"
+#include "utils/waitUntilSignalled.h"
/** @short Extract log level from a CLI option */
spdlog::level::level_enum parseLogLevel(const std::string& name, const docopt::value& option)
@@ -33,18 +29,16 @@
}
static const char usage[] =
- R"(Monitor system health status.
+ R"(Hardware monitoring via Sysrepo.
Usage:
- veliad
+ veliad-hardware
[--appliance=<Model>]
[--log-level=<Level>]
- [--health-log-level=<Level>]
[--sysrepo-log-level=<Level>]
[--hardware-log-level=<Level>]
- [--systemd-ignore-unit=<Unit>]...
- veliad (-h | --help)
- veliad --version
+ veliad-hardware (-h | --help)
+ veliad-hardware --version
Options:
-h --help Show this screen.
@@ -53,14 +47,10 @@
--log-level=<N> Log level for everything [default: 3]
(0 -> critical, 1 -> error, 2 -> warning, 3 -> info,
4 -> debug, 5 -> trace)
- --health-log-level=<N> Log level for the health monitoring [default: 3]
--sysrepo-log-level=<N> Log level for the sysrepo library [default: 3]
--hardware-log-level=<N> Log level for the hardware drivers [default: 3]
- --systemd-ignore-unit=<Unit> Ignore state of systemd's unit in systemd state tracker. Can be specified multiple times.
)";
-DBUS_EVENTLOOP_INIT
-
int main(int argc, char* argv[])
{
std::shared_ptr<spdlog::sinks::sink> loggingSink;
@@ -70,15 +60,15 @@
loggingSink = std::make_shared<spdlog::sinks::ansicolor_stderr_sink_mt>();
}
- auto args = docopt::docopt(usage, {argv + 1, argv + argc}, true, "veliad " VELIA_VERSION, true);
+ auto args = docopt::docopt(usage, {argv + 1, argv + argc}, true, "veliad-system " VELIA_VERSION, true);
velia::utils::initLogs(loggingSink);
spdlog::set_level(spdlog::level::info);
try {
spdlog::set_level(parseLogLevel("Generic", args["--log-level"]));
- spdlog::get("hardware")->set_level(parseLogLevel("Hardware loggers", args["--hardware-log-level"]));
spdlog::get("sysrepo")->set_level(parseLogLevel("Sysrepo library", args["--sysrepo-log-level"]));
+ spdlog::get("hardware")->set_level(parseLogLevel("Hardware loggers", args["--hardware-log-level"]));
spdlog::get("main")->debug("Opening Sysrepo connection");
auto srConn = std::make_shared<sysrepo::Connection>();
@@ -97,30 +87,7 @@
spdlog::get("main")->debug("Initializing Sysrepo ietf-hardware callback");
auto sysrepoIETFHardware = velia::ietf_hardware::sysrepo::Sysrepo(srSubscription, ietfHardware);
- DBUS_EVENTLOOP_START
-
- // health
- auto manager = std::make_shared<velia::health::StateManager>();
-
- // output configuration
- spdlog::get("main")->debug("Initializing LED drivers");
- if (const auto& appliance = args["--appliance"]) {
- manager->m_outputSignal.connect(velia::health::createOutput(appliance.asString()));
- }
-
- spdlog::get("main")->debug("All outputs initialized.");
-
- // input configuration
- std::set<std::string> ignoredUnits(args["--systemd-ignore-unit"].asStringList().begin(), args["--systemd-ignore-unit"].asStringList().end());
- spdlog::get("main")->debug("Starting DBus systemd watcher");
- if (!ignoredUnits.empty()) {
- spdlog::get("main")->debug("Systemd input will ignore changes of the following units: {}", args["--systemd-ignore-unit"]);
- }
- auto inputSystemdDbus = std::make_shared<velia::health::DbusSystemdInput>(manager, ignoredUnits, *g_dbusConnection);
-
- spdlog::get("main")->debug("All inputs initialized.");
-
- DBUS_EVENTLOOP_END;
+ waitUntilSignaled();
return 0;
} catch (std::exception& e) {
diff --git a/src/main.cpp b/src/main-health.cpp
similarity index 78%
rename from src/main.cpp
rename to src/main-health.cpp
index cc8f0e6..c3e4475 100644
--- a/src/main.cpp
+++ b/src/main-health.cpp
@@ -4,12 +4,11 @@
#include <spdlog/sinks/ansicolor_sink.h>
#include <spdlog/spdlog.h>
#include <sysrepo-cpp/Session.hpp>
-#include "Factory.h"
#include "VELIA_VERSION.h"
+#include "health/Factory.h"
#include "health/inputs/DbusSystemdInput.h"
#include "health/manager/StateManager.h"
#include "health/outputs/callables.h"
-#include "ietf-hardware/sysrepo/Sysrepo.h"
#include "main.h"
#include "utils/exceptions.h"
#include "utils/journal.h"
@@ -36,26 +35,23 @@
R"(Monitor system health status.
Usage:
- veliad
+ veliad-health
[--appliance=<Model>]
[--log-level=<Level>]
[--health-log-level=<Level>]
[--sysrepo-log-level=<Level>]
- [--hardware-log-level=<Level>]
[--systemd-ignore-unit=<Unit>]...
- veliad (-h | --help)
- veliad --version
+ veliad-health (-h | --help)
+ veliad-health --version
Options:
-h --help Show this screen.
--version Show version.
- --appliance=<Model> Initialize IETF Hardware and outputs for specific appliance.
--log-level=<N> Log level for everything [default: 3]
(0 -> critical, 1 -> error, 2 -> warning, 3 -> info,
4 -> debug, 5 -> trace)
--health-log-level=<N> Log level for the health monitoring [default: 3]
--sysrepo-log-level=<N> Log level for the sysrepo library [default: 3]
- --hardware-log-level=<N> Log level for the hardware drivers [default: 3]
--systemd-ignore-unit=<Unit> Ignore state of systemd's unit in systemd state tracker. Can be specified multiple times.
)";
@@ -70,14 +66,13 @@
loggingSink = std::make_shared<spdlog::sinks::ansicolor_stderr_sink_mt>();
}
- auto args = docopt::docopt(usage, {argv + 1, argv + argc}, true, "veliad " VELIA_VERSION, true);
+ auto args = docopt::docopt(usage, {argv + 1, argv + argc}, true, "veliad-health " VELIA_VERSION, true);
velia::utils::initLogs(loggingSink);
spdlog::set_level(spdlog::level::info);
try {
spdlog::set_level(parseLogLevel("Generic", args["--log-level"]));
- spdlog::get("hardware")->set_level(parseLogLevel("Hardware loggers", args["--hardware-log-level"]));
spdlog::get("sysrepo")->set_level(parseLogLevel("Sysrepo library", args["--sysrepo-log-level"]));
spdlog::get("main")->debug("Opening Sysrepo connection");
@@ -85,18 +80,6 @@
auto srSess = std::make_shared<sysrepo::Session>(srConn);
auto srSubscription = std::make_shared<sysrepo::Subscribe>(srSess);
- // initialize ietf-hardware
- spdlog::get("main")->debug("Initializing IETFHardware module");
- std::shared_ptr<velia::ietf_hardware::IETFHardware> ietfHardware;
- if (const auto& appliance = args["--appliance"]) {
- ietfHardware = velia::ietf_hardware::create(appliance.asString());
- } else {
- ietfHardware = std::make_shared<velia::ietf_hardware::IETFHardware>();
- }
-
- spdlog::get("main")->debug("Initializing Sysrepo ietf-hardware callback");
- auto sysrepoIETFHardware = velia::ietf_hardware::sysrepo::Sysrepo(srSubscription, ietfHardware);
-
DBUS_EVENTLOOP_START
// health
diff --git a/src/main-system.cpp b/src/main-system.cpp
index 3f94091..843c19e 100644
--- a/src/main-system.cpp
+++ b/src/main-system.cpp
@@ -4,6 +4,7 @@
#include <sysrepo-cpp/Session.hpp>
#include "VELIA_VERSION.h"
#include "main.h"
+#include "system/CzechlightSystem.h"
#include "system/IETFSystem.h"
#include "utils/exceptions.h"
#include "utils/journal.h"
@@ -76,6 +77,7 @@
// initialize ietf-system
spdlog::get("main")->debug("Initializing Sysrepo for system models");
auto sysrepoIETFSystem = velia::system::IETFSystem(srSess, "/etc/os-release");
+ auto sysrepoCzechlightSystem = velia::system::CzechlightSystem(srConn, *g_dbusConnection);
DBUS_EVENTLOOP_END
return 0;
diff --git a/src/system/CzechlightSystem.cpp b/src/system/CzechlightSystem.cpp
new file mode 100644
index 0000000..4256015
--- /dev/null
+++ b/src/system/CzechlightSystem.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
+ *
+ */
+
+#include "CzechlightSystem.h"
+#include "utils/log.h"
+#include "utils/sysrepo.h"
+
+using namespace std::literals;
+
+namespace {
+
+const auto CZECHLIGHT_SYSTEM_MODULE_NAME = "czechlight-system"s;
+const auto CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX = "/"s + CZECHLIGHT_SYSTEM_MODULE_NAME + ":firmware/"s;
+
+}
+
+namespace velia::system {
+
+CzechlightSystem::CzechlightSystem(std::shared_ptr<::sysrepo::Connection> srConn, sdbus::IConnection& dbusConnection)
+ : m_srConn(std::move(srConn))
+ , m_srSession(std::make_shared<::sysrepo::Session>(m_srConn))
+ , m_srSubscribe(std::make_shared<::sysrepo::Subscribe>(m_srSession))
+ , m_rauc(std::make_shared<RAUC>(
+ dbusConnection,
+ [this](const std::string& operation) {
+ if (operation == "installing") {
+ std::map<std::string, std::string> data {
+ {CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/status", "in-progress"},
+ };
+
+ utils::valuesPush(data, std::make_shared<::sysrepo::Session>(m_srConn, SR_DS_OPERATIONAL));
+ }
+ },
+ [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},
+ {CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/status", retVal == 0 ? "succeeded" : "failed"},
+ };
+
+ utils::valuesPush(data, std::make_shared<::sysrepo::Session>(m_srConn, SR_DS_OPERATIONAL));
+ }))
+ , m_log(spdlog::get("system"))
+{
+ {
+ auto raucOperation = m_rauc->operation();
+ auto raucLastError = m_rauc->lastError();
+ std::map<std::string, std::string> data {
+ {CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/message", raucLastError},
+ };
+
+ if (raucOperation == "installing") {
+ data[CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/status"] = "in-progress";
+ } else if (!raucLastError.empty()) {
+ data[CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/status"] = "failed";
+ } else {
+ data[CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/status"] = "none";
+ }
+
+ utils::valuesPush(data, m_srSession, SR_DS_OPERATIONAL);
+ }
+
+ m_srSubscribe->rpc_subscribe(
+ (CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/install").c_str(),
+ [this](::sysrepo::S_Session session, [[maybe_unused]] const char* op_path, const ::sysrepo::S_Vals input, [[maybe_unused]] sr_event_t event, [[maybe_unused]] uint32_t request_id, [[maybe_unused]] ::sysrepo::S_Vals_Holder output) {
+ try {
+ std::string source = input->val(0)->val_to_string();
+ m_rauc->install(source);
+ } catch (sdbus::Error& e) {
+ m_log->warn("RAUC install error: '{}'", e.what());
+ session->set_error(e.getMessage().c_str(), nullptr);
+ return SR_ERR_OPERATION_FAILED;
+ }
+ return SR_ERR_OK;
+ },
+ 0,
+ SR_SUBSCR_CTX_REUSE);
+}
+}
diff --git a/src/system/CzechlightSystem.h b/src/system/CzechlightSystem.h
new file mode 100644
index 0000000..f03173f
--- /dev/null
+++ b/src/system/CzechlightSystem.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
+ *
+ */
+#pragma once
+
+#include <filesystem>
+#include <sdbus-c++/sdbus-c++.h>
+#include <sysrepo-cpp/Session.hpp>
+#include "system/RAUC.h"
+#include "utils/log-fwd.h"
+
+namespace velia::system {
+
+class CzechlightSystem {
+public:
+ CzechlightSystem(std::shared_ptr<::sysrepo::Connection> srConn, sdbus::IConnection& dbusConnection);
+
+private:
+ std::shared_ptr<::sysrepo::Connection> m_srConn;
+ std::shared_ptr<::sysrepo::Session> m_srSession;
+ std::shared_ptr<::sysrepo::Subscribe> m_srSubscribe;
+ std::shared_ptr<RAUC> m_rauc;
+ velia::Log m_log;
+};
+}
diff --git a/tests/mock/sysrepo/events.cpp b/tests/mock/sysrepo/events.cpp
new file mode 100644
index 0000000..7ba2123
--- /dev/null
+++ b/tests/mock/sysrepo/events.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016-2018 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
+ *
+*/
+
+#include "events.h"
+
+namespace {
+std::string sr_ev_notif_type_to_string(const sr_ev_notif_type_t notif_type)
+{
+ switch (notif_type) {
+ case SR_EV_NOTIF_REALTIME:
+ return "SR_EV_NOTIF_REALTIME";
+ case SR_EV_NOTIF_REPLAY:
+ return "SR_EV_NOTIF_REPLAY";
+ case SR_EV_NOTIF_REPLAY_COMPLETE:
+ return "SR_EV_NOTIF_REPLAY_COMPLETE";
+ case SR_EV_NOTIF_STOP:
+ return "SR_EV_NOTIF_STOP";
+ default:
+ return "[unknown event type]";
+ }
+}
+}
+
+EventWatcher::~EventWatcher()
+{
+}
+
+void EventWatcher::operator()(
+ [[maybe_unused]] ::sysrepo::S_Session session,
+ const sr_ev_notif_type_t notif_type,
+ const char* xpath,
+ const ::sysrepo::S_Vals vals,
+ time_t timestamp)
+{
+ Event e;
+ e.xPath = xpath;
+ e.received = std::chrono::steady_clock::now();
+
+ auto log = spdlog::get("main");
+ log->info("SR event {} {} {}", sr_ev_notif_type_to_string(notif_type), timestamp, xpath);
+
+ for (size_t i = 0; i < vals->val_cnt(); ++i) {
+ const auto& v = vals->val(i);
+ auto s = v->val_to_string();
+ log->debug(" {}: {}", v->xpath(), s);
+ e.data[v->xpath()] = s;
+ }
+
+ std::lock_guard<std::mutex> lock(*mutex);
+ events->push_back(e);
+}
+
+std::vector<EventWatcher::Event>::size_type EventWatcher::count() const
+{
+ std::lock_guard<std::mutex> lock(*mutex);
+ return events->size();
+}
+
+EventWatcher::Event EventWatcher::peek(const std::vector<EventWatcher::Event>::size_type index) const
+{
+ std::lock_guard<std::mutex> lock(*mutex);
+ return (*events)[index];
+}
diff --git a/tests/mock/sysrepo/events.h b/tests/mock/sysrepo/events.h
new file mode 100644
index 0000000..7177dce
--- /dev/null
+++ b/tests/mock/sysrepo/events.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016-2018 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
+ *
+*/
+
+#pragma once
+
+#include <chrono>
+#include <mutex>
+#include <sysrepo-cpp/Session.hpp>
+#include "test_log_setup.h"
+
+class EventWatcher {
+public:
+ ~EventWatcher();
+ void operator()(::sysrepo::S_Session session, const sr_ev_notif_type_t notif_type, const char* xpath, const ::sysrepo::S_Vals vals, time_t timestamp);
+
+ struct Event {
+ std::string xPath;
+ std::map<std::string, std::string> data;
+ std::chrono::time_point<std::chrono::steady_clock> received;
+ };
+
+ Event peek(std::vector<Event>::size_type index) const;
+ std::vector<Event>::size_type count() const;
+
+private:
+ mutable std::shared_ptr<std::mutex> mutex = std::make_shared<std::mutex>();
+ std::shared_ptr<std::vector<Event>> events = std::make_shared<std::vector<Event>>();
+};
diff --git a/tests/sysrepo_system-czechlightsystem.cpp b/tests/sysrepo_system-czechlightsystem.cpp
new file mode 100644
index 0000000..fe0f2a2
--- /dev/null
+++ b/tests/sysrepo_system-czechlightsystem.cpp
@@ -0,0 +1,176 @@
+#include "trompeloeil_doctest.h"
+#include "dbus-helpers/dbus_rauc_server.h"
+#include "pretty_printers.h"
+#include "system/CzechlightSystem.h"
+#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;
+
+TEST_CASE("czechlight-system")
+{
+ trompeloeil::sequence seq1;
+
+ TEST_SYSREPO_INIT_LOGS;
+ TEST_SYSREPO_INIT;
+ TEST_SYSREPO_INIT_CLIENT;
+
+ auto dbusServerConnection = sdbus::createSessionBusConnection("de.pengutronix.rauc");
+ auto dbusClientConnection = sdbus::createSessionBusConnection();
+ dbusClientConnection->enterEventLoopAsync();
+ dbusServerConnection->enterEventLoopAsync();
+
+ std::map<std::string, velia::system::RAUC::SlotProperties> dbusRaucStatus = {
+ {"rootfs.1", {
+ {"activated.count", uint32_t {39}},
+ {"activated.timestamp", "2021-01-13T17:20:18Z"},
+ {"bootname", "B"},
+ {"boot-status", "good"},
+ {"bundle.compatible", "czechlight-clearfog"},
+ {"bundle.version", "v4-103-g34d2f48"},
+ {"class", "rootfs"},
+ {"device", "/dev/mmcblk0p3"},
+ {"installed.count", uint32_t {39}},
+ {"installed.timestamp", "2021-01-13T17:20:15Z"},
+ {"mountpoint", "/"},
+ {"sha256", "07b30d065c7aad64d2006ce99fd339c929d3ca97b666fca4584b9ef726469fc4"},
+ {"size", uint64_t {45601892}},
+ {"state", "booted"},
+ {"status", "ok"},
+ {"type", "ext4"},
+ }},
+ {"rootfs.0", {
+ {"activated.count", uint32_t {41}},
+ {"activated.timestamp", "2021-01-13T17:15:54Z"},
+ {"bootname", "A"},
+ {"boot-status", "bad"},
+ {"bundle.compatible", "czechlight-clearfog"},
+ {"bundle.version", "v4-104-ge80fcd4"},
+ {"class", "rootfs"},
+ {"device", "/dev/mmcblk0p1"},
+ {"installed.count", uint32_t {41}},
+ {"installed.timestamp", "2021-01-13T17:15:50Z"},
+ {"sha256", "6d81e8f341edd17c127811f7347c7e23d18c2fc25c0bdc29ac56999cc9c25629"},
+ {"size", uint64_t {45647664}},
+ {"state", "inactive"},
+ {"status", "ok"},
+ {"type", "ext4"},
+ }},
+ {"cfg.1", {
+ {"bundle.compatible", "czechlight-clearfog"},
+ {"bundle.version", "v4-103-g34d2f48"},
+ {"class", "cfg"},
+ {"device", "/dev/mmcblk0p4"},
+ {"installed.count", uint32_t {39}},
+ {"installed.timestamp", "2021-01-13T17:20:18Z"},
+ {"mountpoint", "/cfg"},
+ {"parent", "rootfs.1"},
+ {"sha256", "5ca1b6c461fc194055d52b181f57c63dc1d34c19d041f6395e6f6abc039692bb"},
+ {"size", uint64_t {108}},
+ {"state", "active"},
+ {"status", "ok"},
+ {"type", "ext4"},
+ }},
+ {"cfg.0", {
+ {"bundle.compatible", "czechlight-clearfog"},
+ {"bundle.version", "v4-104-ge80fcd4"},
+ {"class", "cfg"},
+ {"device", "/dev/mmcblk0p2"},
+ {"installed.count", uint32_t {41}},
+ {"installed.timestamp", "2021-01-13T17:15:54Z"},
+ {"parent", "rootfs.0"},
+ {"sha256", "5ca1b6c461fc194055d52b181f57c63dc1d34c19d041f6395e6f6abc039692bb"},
+ {"size", uint64_t {108}},
+ {"state", "inactive"},
+ {"status", "ok"},
+ {"type", "ext4"},
+ }},
+ };
+ auto raucServer = DBusRAUCServer(*dbusServerConnection, "rootfs.1", dbusRaucStatus);
+ auto sysrepo = std::make_shared<velia::system::CzechlightSystem>(srConn, *dbusClientConnection);
+
+ REQUIRE(dataFromSysrepo(client, "/czechlight-system:firmware", SR_DS_OPERATIONAL) == std::map<std::string, std::string>{
+ {"/installation", ""},
+ {"/installation/message", ""},
+ {"/installation/status", "none"},
+ });
+
+ SECTION("Firmware install RPC")
+ {
+ auto rpcInput = std::make_shared<sysrepo::Vals>(1);
+ rpcInput->val(0)->set("/czechlight-system:firmware/installation/install/url", "/path/to/bundle/update.raucb");
+
+ SECTION("Installation runs")
+ {
+ DBusRAUCServer::InstallBehaviour installType;
+ std::map<std::string, std::string> expectedFinished, expectedInProgress = {
+ {"/installation", ""},
+ {"/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")
+ {
+ installType = DBusRAUCServer::InstallBehaviour::OK;
+ expectedFinished = {
+ {"/installation", ""},
+ {"/installation/message", ""},
+ {"/installation/status", "succeeded"},
+ };
+ expectedNotificationsCount = 22;
+ expectedLastNotificationMsg = "Installing done.";
+ }
+
+ SECTION("Unsuccessfull install")
+ {
+ installType = DBusRAUCServer::InstallBehaviour::FAILURE;
+ expectedFinished = {
+ {"/installation", ""},
+ {"/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);
+ auto res = client->rpc_send("/czechlight-system:firmware/installation/install", rpcInput);
+ REQUIRE(res->val_cnt() == 0);
+
+ std::this_thread::sleep_for(10ms); // lets wait a while, so the RAUC's callback for operation changed takes place
+ REQUIRE(dataFromSysrepo(client, "/czechlight-system:firmware", SR_DS_OPERATIONAL) == expectedInProgress);
+
+ 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")
+ {
+ client->rpc_send("/czechlight-system:firmware/installation/install", rpcInput);
+ std::this_thread::sleep_for(10ms);
+ REQUIRE_THROWS_WITH_AS(client->rpc_send("/czechlight-system:firmware/installation/install", rpcInput), "User callback failed", sysrepo::sysrepo_exception);
+ }
+ }
+}
diff --git a/tests/system_rauc.cpp b/tests/system_rauc.cpp
index a60d0fe..1c13846 100644
--- a/tests/system_rauc.cpp
+++ b/tests/system_rauc.cpp
@@ -95,7 +95,7 @@
*clientConnection,
[&fakeRaucInstallCb](const std::string& operation) { fakeRaucInstallCb.operationCallback(operation); },
[&fakeRaucInstallCb](int32_t perc, const std::string& msg) { fakeRaucInstallCb.progressCallback(perc, msg); },
- [&fakeRaucInstallCb](int32_t retval, const std::string& lastError) { fakeRaucInstallCb.completedCallback(retval, lastError); });
+ [&fakeRaucInstallCb](int32_t retVal, const std::string& lastError) { fakeRaucInstallCb.completedCallback(retVal, lastError); });
REQUIRE(rauc->lastError() == "");
REQUIRE(rauc->operation() == "idle");
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);