blob: 023b8807c2e543fbea36e2263761bbaf468e889e [file] [log] [blame]
Tomáš Peckacb7a5f82021-01-20 15:12:00 +01001/*
2 * Copyright (C) 2021 CESNET, https://photonics.cesnet.cz/
3 *
4 * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
5 *
6 */
7
Václav Kubernátb7f26352021-11-26 14:58:33 +01008#include <regex>
Tomáš Pecka594a6762021-01-29 11:06:08 +01009#include "Firmware.h"
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010010#include "utils/log.h"
11#include "utils/sysrepo.h"
12
13using namespace std::literals;
14
15namespace {
16
17const auto CZECHLIGHT_SYSTEM_MODULE_NAME = "czechlight-system"s;
18const auto CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX = "/"s + CZECHLIGHT_SYSTEM_MODULE_NAME + ":firmware/"s;
Tomáš Pecka92b14e42021-01-27 17:33:11 +010019const auto FIRMWARE_SLOTS = {"rootfs.0"s, "rootfs.1"s};
Václav Kubernátb7f26352021-11-26 14:58:33 +010020// Modified regex of yang:date-and-time
21const auto DATE_TIME_REGEX = std::regex{R"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)"};
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010022}
23
24namespace velia::system {
25
Václav Kubernát7efd6d52021-11-09 01:31:11 +010026Firmware::Firmware(::sysrepo::Connection srConn, sdbus::IConnection& dbusConnectionSignals, sdbus::IConnection& dbusConnectionMethods)
Tomáš Pecka92b14e42021-01-27 17:33:11 +010027 : m_rauc(std::make_shared<RAUC>(
28 dbusConnectionSignals,
29 dbusConnectionMethods,
30 [this](const std::string& operation) {
31 if (operation == "installing") {
32 std::lock_guard<std::mutex> lck(m_mtx);
Tomáš Pecka0eefdf12021-02-17 19:28:13 +010033 m_installMessage = "";
Tomáš Pecka92b14e42021-01-27 17:33:11 +010034 m_installStatus = "in-progress";
35 }
36 },
37 [this](int32_t perc, const std::string& msg) {
38 std::map<std::string, std::string> data = {
39 {CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/update/message", msg},
40 {CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/update/progress", std::to_string(perc)},
41 };
42
Václav Kubernát7efd6d52021-11-09 01:31:11 +010043 std::optional<libyang::DataNode> dataNode;
44 auto session = m_srConn.sessionStart();
Tomáš Pecka92b14e42021-01-27 17:33:11 +010045
Tomáš Pecka498e91c2021-03-02 17:46:47 +010046 utils::valuesToYang(data, {}, session, dataNode);
Václav Kubernát7efd6d52021-11-09 01:31:11 +010047 session.sendNotification(*dataNode, sysrepo::Wait::No); // No need to wait, it's just a notification.
Tomáš Pecka92b14e42021-01-27 17:33:11 +010048 },
49 [this](int32_t retVal, const std::string& lastError) {
50 auto lock = updateSlotStatus();
51 m_installStatus = retVal == 0 ? "succeeded" : "failed";
52 m_installMessage = lastError;
53 }))
54 , m_log(spdlog::get("system"))
55 , m_srConn(std::move(srConn))
Václav Kubernát7efd6d52021-11-09 01:31:11 +010056 , m_srSessionOps(m_srConn.sessionStart())
57 , m_srSessionRPC(m_srConn.sessionStart())
58 , m_srSubscribeOps()
59 , m_srSubscribeRPC()
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010060{
Tomáš Peckab78372f2021-04-22 11:09:20 +020061 utils::ensureModuleImplemented(m_srSessionOps, "czechlight-system", "2021-01-13");
62
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010063 {
64 auto raucOperation = m_rauc->operation();
65 auto raucLastError = m_rauc->lastError();
Tomáš Peckaaf8f0632021-01-27 16:45:55 +010066
Tomáš Pecka92b14e42021-01-27 17:33:11 +010067 auto lock = updateSlotStatus();
Tomáš Peckaaf8f0632021-01-27 16:45:55 +010068
69 m_installMessage = raucLastError;
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010070
71 if (raucOperation == "installing") {
Tomáš Peckaaf8f0632021-01-27 16:45:55 +010072 m_installStatus = "in-progress";
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010073 } else if (!raucLastError.empty()) {
Tomáš Peckaaf8f0632021-01-27 16:45:55 +010074 m_installStatus = "failed";
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010075 } else {
Tomáš Peckaaf8f0632021-01-27 16:45:55 +010076 m_installStatus = "none";
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010077 }
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010078 }
79
Václav Kubernát7efd6d52021-11-09 01:31:11 +010080 ::sysrepo::RpcActionCb cbRPC = [this](auto session, auto, auto, auto input, auto, auto, auto) {
81 auto lock = updateSlotStatus();
82
83 try {
84 auto source = std::string{input.findPath("url")->asTerm().valueStr()};
85 m_rauc->install(source);
86 } catch (sdbus::Error& e) {
87 m_log->warn("RAUC install error: '{}'", e.what());
88 utils::setErrors(session, e.getMessage());
89 return ::sysrepo::ErrorCode::OperationFailed;
90 }
91 return ::sysrepo::ErrorCode::Ok;
92 };
93
Jan Kundrátb3e99982022-03-18 17:38:20 +010094 m_srSubscribeRPC = m_srSessionRPC.onRPCAction(CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/install", cbRPC);
Václav Kubernát7efd6d52021-11-09 01:31:11 +010095
96 ::sysrepo::OperGetCb cbOper = [this](auto session, auto, auto, auto, auto, auto, auto& parent) {
97 std::map<std::string, std::string> data;
98
99 {
Tomáš Pecka92b14e42021-01-27 17:33:11 +0100100 auto lock = updateSlotStatus();
101
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100102 data.insert(m_slotStatusCache.begin(), m_slotStatusCache.end());
103 data[CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/status"] = m_installStatus;
104 data[CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/message"] = m_installMessage;
105 }
Tomáš Peckaaf8f0632021-01-27 16:45:55 +0100106
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100107 utils::valuesToYang(data, {}, session, parent);
108 return ::sysrepo::ErrorCode::Ok;
109 };
110
111 m_srSubscribeOps = m_srSessionOps.onOperGet(
Jan Kundrátb3e99982022-03-18 17:38:20 +0100112 CZECHLIGHT_SYSTEM_MODULE_NAME,
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100113 cbOper,
Jan Kundrátb3e99982022-03-18 17:38:20 +0100114 CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "*",
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100115 ::sysrepo::SubscribeOptions::Passive | ::sysrepo::SubscribeOptions::OperMerge);
Tomáš Peckacb7a5f82021-01-20 15:12:00 +0100116}
Tomáš Pecka92b14e42021-01-27 17:33:11 +0100117
118/** @brief Updates the slot status cache with the new data obtained via RAUC
119 *
120 * Gets current slot status data from RAUC and updates the local slot status cache if new data are available.
121 * The methods manipulates with the local cache which is shared among multiple thread.
122 *
123 * @return an unique_lock (in locked state) that can be further used to manipulate with the local cache
124 * */
125std::unique_lock<std::mutex> Firmware::updateSlotStatus()
126{
127 std::map<std::string, velia::system::RAUC::SlotProperties> slotStatus;
128
129 try {
130 slotStatus = m_rauc->slotStatus();
131 } catch (sdbus::Error& e) {
132 m_log->warn("Could not fetch RAUC slot status data: {}", e.getMessage());
133 }
134
135 std::unique_lock<std::mutex> lck(m_mtx);
136
137 for (const auto& slotName : FIRMWARE_SLOTS) {
138 if (auto it = slotStatus.find(slotName); it != slotStatus.end()) { // if there is an update for the slot "slotName"
Václav Kubernátb7f26352021-11-26 14:58:33 +0100139 auto& props = it->second;
Tomáš Pecka825542f2021-02-24 14:00:10 +0100140 std::string xpathPrefix;
Tomáš Pecka92b14e42021-01-27 17:33:11 +0100141
Tomáš Pecka825542f2021-02-24 14:00:10 +0100142 // 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).
143 if (auto pit = props.find("bootname"); pit != props.end()) {
144 xpathPrefix = CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "firmware-slot[name='" + std::get<std::string>(pit->second) + "']/";
145 } else {
146 m_log->error("RAUC didn't provide 'bootname' property for slot '{}'. Skipping update for that slot.");
147 continue;
148 }
149
Václav Kubernátb7f26352021-11-26 14:58:33 +0100150 // sysrepo needs the "-00:00" suffix instead of the "Z" suffix.
151 if (auto pit = props.find("installed.timestamp"); pit != props.end()) {
152 auto& ts = std::get<std::string>(pit->second);
153 if (std::regex_match(ts, DATE_TIME_REGEX)) {
154 ts.pop_back(); // Get rid of the "Z".
155 ts.append("-00:00");
156 } else {
157 m_log->warn("RAUC provided a timestamp in an unexpected format: {}", ts);
158 }
159 }
160
Tomáš Pecka825542f2021-02-24 14:00:10 +0100161 for (const auto& [yangKey, raucKey] : {std::pair{"state", "state"}, {"boot-status", "boot-status"}, {"version", "bundle.version"}, {"installed", "installed.timestamp"}}) {
162 if (auto pit = props.find(raucKey); pit != props.end()) {
163 m_slotStatusCache[xpathPrefix + yangKey] = std::get<std::string>(pit->second);
164 } else {
165 m_log->warn("RAUC didn't provide '{}' property for slot '{}'.", raucKey, slotName);
166 }
167 }
Tomáš Pecka92b14e42021-01-27 17:33:11 +0100168 }
169 }
170
171 return lck;
172}
Tomáš Peckacb7a5f82021-01-20 15:12:00 +0100173}