Merge changes Ic2bdd047,If20abcda,I90edcf87,I6c42ae2b,I703c6982, ...
* changes:
system: fix log level setting in velia-system
yang: disable ipv6/autoconf in ietf-ip model
utils: Fix no data in valuesPush results in segfault
system: Implement ietf-interfaces
system: Get link info from rtnetlink
utils: log libyang manipulation
utils: Allow to remove values from sysrepo
CI: install libnl
yang: czechlight-network (ietf-interfaces, ietf-ip) model
diff --git a/ci/build.sh b/ci/build.sh
index 5cc60ab..2631003 100755
--- a/ci/build.sh
+++ b/ci/build.sh
@@ -19,13 +19,6 @@
export CC=clang
export CXX=clang++
export LD=clang
- export CXXFLAGS="-stdlib=libc++"
- # spdlog passes std::string instances, and it's built against GCC's libstdc++ on the CI
- sed -i \
- -e 's/spdlog::spdlog/spdlog::spdlog_header_only/' \
- -e '/find_package(spdlog/ a add_definitions(-DFMT_HEADER_ONLY)' \
- -e 's/fmt::fmt/fmt::fmt-header-only/' \
- ${ZUUL_PROJECT_SRC_DIR}/CMakeLists.txt
fi
if [[ $ZUUL_JOB_NAME =~ .*-ubsan ]]; then
diff --git a/src/main-system.cpp b/src/main-system.cpp
index f725d8e..4087607 100644
--- a/src/main-system.cpp
+++ b/src/main-system.cpp
@@ -71,8 +71,7 @@
std::filesystem::create_directories(persistentNetworkDirectory);
auto srSessStartup = std::make_shared<sysrepo::Session>(srConn, SR_DS_STARTUP);
-
- auto sysrepoNetworkStartup = velia::system::Network(srSess, persistentNetworkDirectory, [](const auto&) {});
+ auto sysrepoNetworkStartup = velia::system::Network(srSessStartup, persistentNetworkDirectory, [](const auto&) {});
auto sysrepoNetworkRunning = velia::system::Network(srSess, runtimeNetworkDirectory, [](const auto& reconfiguredInterfaces) {
auto log = spdlog::get("system");
diff --git a/src/system/Firmware.cpp b/src/system/Firmware.cpp
index 844ef47..5408c43 100644
--- a/src/system/Firmware.cpp
+++ b/src/system/Firmware.cpp
@@ -133,12 +133,23 @@
for (const auto& slotName : FIRMWARE_SLOTS) {
if (auto it = slotStatus.find(slotName); it != slotStatus.end()) { // if there is an update for the slot "slotName"
const auto& props = it->second;
- auto xpathPrefix = CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "firmware-slot[name='" + std::get<std::string>(props.at("bootname")) + "']/";
+ std::string xpathPrefix;
- m_slotStatusCache[xpathPrefix + "state"] = std::get<std::string>(props.at("state"));
- m_slotStatusCache[xpathPrefix + "version"] = std::get<std::string>(props.at("bundle.version"));
- m_slotStatusCache[xpathPrefix + "installed"] = std::get<std::string>(props.at("installed.timestamp"));
- m_slotStatusCache[xpathPrefix + "boot-status"] = std::get<std::string>(props.at("boot-status"));
+ // Better be defensive about provided properties. If somebody removes /slot.raucs, RAUC doesn't provide all the data (at least bundle.version and installed.timestamp).
+ if (auto pit = props.find("bootname"); pit != props.end()) {
+ xpathPrefix = CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "firmware-slot[name='" + std::get<std::string>(pit->second) + "']/";
+ } else {
+ m_log->error("RAUC didn't provide 'bootname' property for slot '{}'. Skipping update for that slot.");
+ continue;
+ }
+
+ for (const auto& [yangKey, raucKey] : {std::pair{"state", "state"}, {"boot-status", "boot-status"}, {"version", "bundle.version"}, {"installed", "installed.timestamp"}}) {
+ if (auto pit = props.find(raucKey); pit != props.end()) {
+ m_slotStatusCache[xpathPrefix + yangKey] = std::get<std::string>(pit->second);
+ } else {
+ m_log->warn("RAUC didn't provide '{}' property for slot '{}'.", raucKey, slotName);
+ }
+ }
}
}
diff --git a/src/system/Network.cpp b/src/system/Network.cpp
index cb8254a..055c3e1 100644
--- a/src/system/Network.cpp
+++ b/src/system/Network.cpp
@@ -53,13 +53,18 @@
CZECHLIGHT_SYSTEM_MODULE_NAME.c_str(),
[&, networkConfigDirectory = std::move(networkConfigDirectory), networkReloadCallback = std::move(networkReloadCallback)](sysrepo::S_Session session, [[maybe_unused]] const char* module_name, [[maybe_unused]] const char* xpath, [[maybe_unused]] sr_event_t event, [[maybe_unused]] uint32_t request_id) {
auto config = getNetworkConfiguration(session, m_log);
+ std::vector<std::string> changedInterfaces;
+
for (const auto& [interface, networkFileContents] : config) {
- velia::utils::safeWriteFile(networkConfigDirectory / (interface + ".network"), networkFileContents);
+ auto targetFile = networkConfigDirectory / (interface + ".network");
+
+ if (!std::filesystem::exists(targetFile) || velia::utils::readFileToString(targetFile) != networkFileContents) { // don't reload if the new file same as the already existing file
+ velia::utils::safeWriteFile(targetFile, networkFileContents);
+ changedInterfaces.push_back(interface);
+ }
}
- std::vector<std::string> interfaces;
- std::transform(config.begin(), config.end(), std::back_inserter(interfaces), [](const auto& kv) { return kv.first; });
- networkReloadCallback(interfaces);
+ networkReloadCallback(changedInterfaces);
return SR_ERR_OK;
},
diff --git a/tests/sysrepo_system-firmware.cpp b/tests/sysrepo_system-firmware.cpp
index 6776518..ef470f1 100644
--- a/tests/sysrepo_system-firmware.cpp
+++ b/tests/sysrepo_system-firmware.cpp
@@ -9,7 +9,7 @@
using namespace std::literals;
-TEST_CASE("Firmware in czechlight-system")
+TEST_CASE("Firmware in czechlight-system, RPC")
{
trompeloeil::sequence seq1;
@@ -245,3 +245,198 @@
}
}
}
+
+TEST_CASE("Firmware in czechlight-system, slot status")
+{
+ trompeloeil::sequence seq1;
+
+ TEST_SYSREPO_INIT_LOGS;
+ TEST_SYSREPO_INIT;
+ TEST_SYSREPO_INIT_CLIENT;
+
+ auto dbusServerConnection = sdbus::createSessionBusConnection("de.pengutronix.rauc");
+ auto dbusClientConnectionSignals = sdbus::createSessionBusConnection();
+ auto dbusClientConnectionMethods = sdbus::createSessionBusConnection();
+ dbusClientConnectionSignals->enterEventLoopAsync();
+ dbusClientConnectionMethods->enterEventLoopAsync();
+ dbusServerConnection->enterEventLoopAsync();
+
+ std::map<std::string, velia::system::RAUC::SlotProperties> dbusRaucStatus;
+ std::map<std::string, std::string> expected;
+
+ SECTION("Complete data")
+ {
+ 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"},
+ }},
+ };
+
+ expected = {
+ {"[name='A']/boot-status", "bad"},
+ {"[name='A']/installed", "2021-01-13T17:15:50Z"},
+ {"[name='A']/name", "A"},
+ {"[name='A']/state", "inactive"},
+ {"[name='A']/version", "v4-104-ge80fcd4"},
+ {"[name='B']/boot-status", "good"},
+ {"[name='B']/installed", "2021-01-13T17:20:15Z"},
+ {"[name='B']/name", "B"},
+ {"[name='B']/state", "booted"},
+ {"[name='B']/version", "v4-103-g34d2f48"},
+ };
+ }
+
+ SECTION("Missing data in rootfs.0")
+ {
+ 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", {
+ {"bootname", "A"},
+ {"boot-status", "bad"},
+ {"class", "rootfs"},
+ {"device", "/dev/mmcblk0p1"},
+ {"sha256", "6d81e8f341edd17c127811f7347c7e23d18c2fc25c0bdc29ac56999cc9c25629"},
+ {"size", uint64_t {45647664}},
+ {"state", "inactive"},
+ {"status", "ok"},
+ {"type", "ext4"},
+ }},
+ };
+
+ expected = {
+ {"[name='A']/boot-status", "bad"},
+ {"[name='A']/name", "A"},
+ {"[name='A']/state", "inactive"},
+ {"[name='B']/boot-status", "good"},
+ {"[name='B']/installed", "2021-01-13T17:20:15Z"},
+ {"[name='B']/name", "B"},
+ {"[name='B']/state", "booted"},
+ {"[name='B']/version", "v4-103-g34d2f48"},
+ };
+ }
+
+ SECTION("Missing bootname in rootfs.0")
+ {
+ 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", {
+ {"boot-status", "bad"},
+ {"class", "rootfs"},
+ {"device", "/dev/mmcblk0p1"},
+ {"sha256", "6d81e8f341edd17c127811f7347c7e23d18c2fc25c0bdc29ac56999cc9c25629"},
+ {"size", uint64_t {45647664}},
+ {"state", "inactive"},
+ {"status", "ok"},
+ {"type", "ext4"},
+ }},
+ };
+
+ expected = {
+ {"[name='B']/boot-status", "good"},
+ {"[name='B']/installed", "2021-01-13T17:20:15Z"},
+ {"[name='B']/name", "B"},
+ {"[name='B']/state", "booted"},
+ {"[name='B']/version", "v4-103-g34d2f48"},
+ };
+ }
+
+ auto raucServer = DBusRAUCServer(*dbusServerConnection, "rootfs.1", dbusRaucStatus);
+ auto sysrepo = std::make_shared<velia::system::Firmware>(srConn, *dbusClientConnectionSignals, *dbusClientConnectionMethods);
+
+ REQUIRE(dataFromSysrepo(client, "/czechlight-system:firmware/firmware-slot", SR_DS_OPERATIONAL) == expected);
+}
diff --git a/tests/sysrepo_system-network.cpp b/tests/sysrepo_system-network.cpp
index ee71ff4..6feeed2 100644
--- a/tests/sysrepo_system-network.cpp
+++ b/tests/sysrepo_system-network.cpp
@@ -7,6 +7,7 @@
#include "trompeloeil_doctest.h"
#include <filesystem>
+#include "fs-helpers/FileInjector.h"
#include "fs-helpers/utils.h"
#include "pretty_printers.h"
#include "system/Network.h"
@@ -66,28 +67,84 @@
client->delete_item(PRESENCE_CONTAINER);
client->apply_changes();
- REQUIRE_CALL(fake, cb(std::vector<std::string> {"eth1"})).IN_SEQUENCE(seq1);
- auto network = std::make_shared<velia::system::Network>(srSess, fakeDir, [&fake](const std::vector<std::string>& updatedInterfaces) { fake.cb(updatedInterfaces); });
-
- REQUIRE(std::filesystem::exists(expectedFilePath));
- REQUIRE(velia::utils::readFileToString(expectedFilePath) == EXPECTED_CONTENT_BRIDGE);
-
- SECTION("Nothing happens")
- {
- }
-
- SECTION("Change: Container present. Switch to DHCP configuration")
+ SECTION("File not present")
{
REQUIRE_CALL(fake, cb(std::vector<std::string> {"eth1"})).IN_SEQUENCE(seq1);
-
- client->set_item(PRESENCE_CONTAINER);
- client->apply_changes();
- waitForCompletionAndBitMore(seq1);
+ auto network = std::make_shared<velia::system::Network>(srSess, fakeDir, [&fake](const std::vector<std::string>& updatedInterfaces) { fake.cb(updatedInterfaces); });
REQUIRE(std::filesystem::exists(expectedFilePath));
- REQUIRE(velia::utils::readFileToString(expectedFilePath) == EXPECTED_CONTENT_DHCP);
+ REQUIRE(velia::utils::readFileToString(expectedFilePath) == EXPECTED_CONTENT_BRIDGE);
+
+ SECTION("Nothing happens")
+ {
+ }
+
+ SECTION("Change: Container present. Switch to DHCP configuration")
+ {
+ REQUIRE_CALL(fake, cb(std::vector<std::string> {"eth1"})).IN_SEQUENCE(seq1);
+
+ client->set_item(PRESENCE_CONTAINER);
+ client->apply_changes();
+ waitForCompletionAndBitMore(seq1);
+
+ REQUIRE(std::filesystem::exists(expectedFilePath));
+ REQUIRE(velia::utils::readFileToString(expectedFilePath) == EXPECTED_CONTENT_DHCP);
+ }
}
+ SECTION("Configuration file already present (with DHCP configuration)")
+ {
+ auto file = std::make_unique<FileInjector>(expectedFilePath, std::filesystem::perms::all, EXPECTED_CONTENT_DHCP);
+ REQUIRE_CALL(fake, cb(std::vector<std::string>{"eth1"})).IN_SEQUENCE(seq1);
+ spdlog::get("main")->error("CONTENT: {}", velia::utils::readFileToString(expectedFilePath));
+ auto network = std::make_shared<velia::system::Network>(srSess, fakeDir, [&fake](const std::vector<std::string>& updatedInterfaces) { fake.cb(updatedInterfaces); });
+
+ REQUIRE(std::filesystem::exists(expectedFilePath));
+ REQUIRE(velia::utils::readFileToString(expectedFilePath) == EXPECTED_CONTENT_BRIDGE);
+
+ SECTION("Nothing happens")
+ {
+ }
+
+ SECTION("Change: Container present. Switch to DHCP configuration")
+ {
+ REQUIRE_CALL(fake, cb(std::vector<std::string> {"eth1"})).IN_SEQUENCE(seq1);
+
+ client->set_item(PRESENCE_CONTAINER);
+ client->apply_changes();
+ waitForCompletionAndBitMore(seq1);
+
+ REQUIRE(std::filesystem::exists(expectedFilePath));
+ REQUIRE(velia::utils::readFileToString(expectedFilePath) == EXPECTED_CONTENT_DHCP);
+ }
+ }
+
+ SECTION("Configuration file already present (with bridge configuration)")
+ {
+ auto file = std::make_unique<FileInjector>(expectedFilePath, std::filesystem::perms::all, EXPECTED_CONTENT_BRIDGE);
+ REQUIRE_CALL(fake, cb(std::vector<std::string>{})).IN_SEQUENCE(seq1);
+ spdlog::get("main")->error("CONTENT: {}", velia::utils::readFileToString(expectedFilePath));
+ auto network = std::make_shared<velia::system::Network>(srSess, fakeDir, [&fake](const std::vector<std::string>& updatedInterfaces) { fake.cb(updatedInterfaces); });
+
+ REQUIRE(std::filesystem::exists(expectedFilePath));
+ REQUIRE(velia::utils::readFileToString(expectedFilePath) == EXPECTED_CONTENT_BRIDGE);
+
+ SECTION("Nothing happens")
+ {
+ }
+
+ SECTION("Change: Container present. Switch to DHCP configuration")
+ {
+ REQUIRE_CALL(fake, cb(std::vector<std::string> {"eth1"})).IN_SEQUENCE(seq1);
+
+ client->set_item(PRESENCE_CONTAINER);
+ client->apply_changes();
+ waitForCompletionAndBitMore(seq1);
+
+ REQUIRE(std::filesystem::exists(expectedFilePath));
+ REQUIRE(velia::utils::readFileToString(expectedFilePath) == EXPECTED_CONTENT_DHCP);
+ }
+ }
}
SECTION("Container present. Start with DHCP configuration")