blob: b1808e4352a4db77884e14ec46a8dcfc404f819a [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"
Jan Kundrát7935e812024-09-17 17:02:01 +020010#include "utils/libyang.h"
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010011#include "utils/log.h"
12#include "utils/sysrepo.h"
13
14using namespace std::literals;
15
16namespace {
17
18const auto CZECHLIGHT_SYSTEM_MODULE_NAME = "czechlight-system"s;
19const auto CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX = "/"s + CZECHLIGHT_SYSTEM_MODULE_NAME + ":firmware/"s;
Tomáš Pecka92b14e42021-01-27 17:33:11 +010020const auto FIRMWARE_SLOTS = {"rootfs.0"s, "rootfs.1"s};
Václav Kubernátb7f26352021-11-26 14:58:33 +010021// Modified regex of yang:date-and-time
22const 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 +010023}
24
25namespace velia::system {
26
Václav Kubernát7efd6d52021-11-09 01:31:11 +010027Firmware::Firmware(::sysrepo::Connection srConn, sdbus::IConnection& dbusConnectionSignals, sdbus::IConnection& dbusConnectionMethods)
Tomáš Pecka92b14e42021-01-27 17:33:11 +010028 : m_rauc(std::make_shared<RAUC>(
29 dbusConnectionSignals,
30 dbusConnectionMethods,
31 [this](const std::string& operation) {
32 if (operation == "installing") {
33 std::lock_guard<std::mutex> lck(m_mtx);
Tomáš Pecka0eefdf12021-02-17 19:28:13 +010034 m_installMessage = "";
Tomáš Pecka92b14e42021-01-27 17:33:11 +010035 m_installStatus = "in-progress";
36 }
37 },
38 [this](int32_t perc, const std::string& msg) {
39 std::map<std::string, std::string> data = {
40 {CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/update/message", msg},
41 {CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/update/progress", std::to_string(perc)},
42 };
43
Václav Kubernát7efd6d52021-11-09 01:31:11 +010044 std::optional<libyang::DataNode> dataNode;
45 auto session = m_srConn.sessionStart();
Tomáš Pecka92b14e42021-01-27 17:33:11 +010046
Jan Kundrát498c3f82023-05-24 19:25:48 +020047 utils::valuesToYang(data, {}, {}, session, dataNode);
Václav Kubernát7efd6d52021-11-09 01:31:11 +010048 session.sendNotification(*dataNode, sysrepo::Wait::No); // No need to wait, it's just a notification.
Tomáš Pecka92b14e42021-01-27 17:33:11 +010049 },
50 [this](int32_t retVal, const std::string& lastError) {
51 auto lock = updateSlotStatus();
52 m_installStatus = retVal == 0 ? "succeeded" : "failed";
53 m_installMessage = lastError;
54 }))
55 , m_log(spdlog::get("system"))
56 , m_srConn(std::move(srConn))
Václav Kubernát7efd6d52021-11-09 01:31:11 +010057 , m_srSessionOps(m_srConn.sessionStart())
58 , m_srSessionRPC(m_srConn.sessionStart())
59 , m_srSubscribeOps()
60 , m_srSubscribeRPC()
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010061{
Jan Kundrát7a30cf42022-07-12 22:24:09 +020062 utils::ensureModuleImplemented(m_srSessionOps, "czechlight-system", "2022-07-08");
Tomáš Peckab78372f2021-04-22 11:09:20 +020063
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010064 {
65 auto raucOperation = m_rauc->operation();
66 auto raucLastError = m_rauc->lastError();
Tomáš Peckaaf8f0632021-01-27 16:45:55 +010067
Tomáš Pecka92b14e42021-01-27 17:33:11 +010068 auto lock = updateSlotStatus();
Tomáš Peckaaf8f0632021-01-27 16:45:55 +010069
70 m_installMessage = raucLastError;
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010071
72 if (raucOperation == "installing") {
Tomáš Peckaaf8f0632021-01-27 16:45:55 +010073 m_installStatus = "in-progress";
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010074 } else if (!raucLastError.empty()) {
Tomáš Peckaaf8f0632021-01-27 16:45:55 +010075 m_installStatus = "failed";
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010076 } else {
Tomáš Peckaaf8f0632021-01-27 16:45:55 +010077 m_installStatus = "none";
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010078 }
Tomáš Peckacb7a5f82021-01-20 15:12:00 +010079 }
80
Václav Kubernát7efd6d52021-11-09 01:31:11 +010081 ::sysrepo::RpcActionCb cbRPC = [this](auto session, auto, auto, auto input, auto, auto, auto) {
82 auto lock = updateSlotStatus();
83
84 try {
Jan Kundrát8dd54de2024-09-17 17:04:00 +020085 auto source = utils::asString(*input.findPath("url"));
Václav Kubernát7efd6d52021-11-09 01:31:11 +010086 m_rauc->install(source);
87 } catch (sdbus::Error& e) {
88 m_log->warn("RAUC install error: '{}'", e.what());
89 utils::setErrors(session, e.getMessage());
90 return ::sysrepo::ErrorCode::OperationFailed;
91 }
92 return ::sysrepo::ErrorCode::Ok;
93 };
94
Jan Kundrátb3e99982022-03-18 17:38:20 +010095 m_srSubscribeRPC = m_srSessionRPC.onRPCAction(CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/install", cbRPC);
Václav Kubernát7efd6d52021-11-09 01:31:11 +010096
Jan Kundrát3795cab2022-07-13 18:08:19 +020097 ::sysrepo::RpcActionCb markSlotAs = [this](auto, auto, auto path, auto input, auto, auto, auto) {
Jan Kundrát8dd54de2024-09-17 17:04:00 +020098 auto bootName = utils::asString(*input.parent()->findPath("name"));
Jan Kundrát3795cab2022-07-13 18:08:19 +020099 std::string action;
100 if (path == CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "firmware-slot/set-active-after-reboot") {
101 action = "active";
102 } else if (path == CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "firmware-slot/set-unhealthy") {
103 action = "bad";
104 } else {
105 throw std::logic_error{"action callback XPath mismatch"};
106 }
107 std::string slot;
108 {
109 auto lock = updateSlotStatus();
110 auto slotIt = m_bootNameToSlot.find(bootName);
111 if (slotIt == m_bootNameToSlot.end()) {
112 throw std::runtime_error{"cannot map FW slot boot name '" + bootName + "' to a RAUC slot name"};
113 }
114 slot = slotIt->second;
115 }
116 m_log->debug("RAUC: marking boot slot {} ({}) as {}", bootName, slot, action);
117 m_rauc->mark(action, slot);
118 return ::sysrepo::ErrorCode::Ok;
119 };
120
121 m_srSubscribeRPC->onRPCAction(CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "firmware-slot/set-active-after-reboot", markSlotAs);
122 m_srSubscribeRPC->onRPCAction(CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "firmware-slot/set-unhealthy", markSlotAs);
123
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100124 ::sysrepo::OperGetCb cbOper = [this](auto session, auto, auto, auto, auto, auto, auto& parent) {
125 std::map<std::string, std::string> data;
126
127 {
Tomáš Pecka92b14e42021-01-27 17:33:11 +0100128 auto lock = updateSlotStatus();
129
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100130 data.insert(m_slotStatusCache.begin(), m_slotStatusCache.end());
131 data[CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/status"] = m_installStatus;
132 data[CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/message"] = m_installMessage;
133 }
Tomáš Peckaaf8f0632021-01-27 16:45:55 +0100134
Jan Kundrát498c3f82023-05-24 19:25:48 +0200135 utils::valuesToYang(data, {}, {}, session, parent);
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100136 return ::sysrepo::ErrorCode::Ok;
137 };
138
139 m_srSubscribeOps = m_srSessionOps.onOperGet(
Jan Kundrátb3e99982022-03-18 17:38:20 +0100140 CZECHLIGHT_SYSTEM_MODULE_NAME,
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100141 cbOper,
Jan Kundrátb3e99982022-03-18 17:38:20 +0100142 CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "*",
Václav Kubernát7efd6d52021-11-09 01:31:11 +0100143 ::sysrepo::SubscribeOptions::Passive | ::sysrepo::SubscribeOptions::OperMerge);
Tomáš Peckacb7a5f82021-01-20 15:12:00 +0100144}
Tomáš Pecka92b14e42021-01-27 17:33:11 +0100145
146/** @brief Updates the slot status cache with the new data obtained via RAUC
147 *
148 * Gets current slot status data from RAUC and updates the local slot status cache if new data are available.
149 * The methods manipulates with the local cache which is shared among multiple thread.
150 *
151 * @return an unique_lock (in locked state) that can be further used to manipulate with the local cache
152 * */
153std::unique_lock<std::mutex> Firmware::updateSlotStatus()
154{
155 std::map<std::string, velia::system::RAUC::SlotProperties> slotStatus;
Jan Kundráte55b56b2022-07-13 12:51:44 +0200156 std::string primarySlot;
Tomáš Pecka92b14e42021-01-27 17:33:11 +0100157
158 try {
159 slotStatus = m_rauc->slotStatus();
Jan Kundráte55b56b2022-07-13 12:51:44 +0200160 primarySlot = m_rauc->primarySlot();
Tomáš Pecka92b14e42021-01-27 17:33:11 +0100161 } catch (sdbus::Error& e) {
162 m_log->warn("Could not fetch RAUC slot status data: {}", e.getMessage());
163 }
164
165 std::unique_lock<std::mutex> lck(m_mtx);
Jan Kundrát3795cab2022-07-13 18:08:19 +0200166 m_bootNameToSlot.clear();
Tomáš Pecka92b14e42021-01-27 17:33:11 +0100167
168 for (const auto& slotName : FIRMWARE_SLOTS) {
169 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 +0100170 auto& props = it->second;
Tomáš Pecka825542f2021-02-24 14:00:10 +0100171 std::string xpathPrefix;
Tomáš Pecka92b14e42021-01-27 17:33:11 +0100172
Tomáš Pecka825542f2021-02-24 14:00:10 +0100173 // 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).
174 if (auto pit = props.find("bootname"); pit != props.end()) {
175 xpathPrefix = CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "firmware-slot[name='" + std::get<std::string>(pit->second) + "']/";
Jan Kundrát3795cab2022-07-13 18:08:19 +0200176 m_bootNameToSlot[std::get<std::string>(pit->second)] = slotName;
Tomáš Pecka825542f2021-02-24 14:00:10 +0100177 } else {
Jan Kundráte26ac362023-12-19 18:04:27 +0100178 m_log->error("RAUC didn't provide 'bootname' property for slot '{}'. Skipping update for that slot.", slotName);
Tomáš Pecka825542f2021-02-24 14:00:10 +0100179 continue;
180 }
181
Václav Kubernátb7f26352021-11-26 14:58:33 +0100182 // sysrepo needs the "-00:00" suffix instead of the "Z" suffix.
183 if (auto pit = props.find("installed.timestamp"); pit != props.end()) {
184 auto& ts = std::get<std::string>(pit->second);
185 if (std::regex_match(ts, DATE_TIME_REGEX)) {
186 ts.pop_back(); // Get rid of the "Z".
187 ts.append("-00:00");
188 } else {
189 m_log->warn("RAUC provided a timestamp in an unexpected format: {}", ts);
190 }
191 }
192
Jan Kundrát58b39ae2022-07-08 18:54:10 +0200193 for (const auto& [yangKey, raucKey] : {std::pair{"version", "bundle.version"}, {"installed", "installed.timestamp"}}) {
Tomáš Pecka825542f2021-02-24 14:00:10 +0100194 if (auto pit = props.find(raucKey); pit != props.end()) {
195 m_slotStatusCache[xpathPrefix + yangKey] = std::get<std::string>(pit->second);
196 } else {
197 m_log->warn("RAUC didn't provide '{}' property for slot '{}'.", raucKey, slotName);
198 }
199 }
Jan Kundrát58b39ae2022-07-08 18:54:10 +0200200
201 if (auto pit = props.find("state"); pit != props.end()) {
202 m_slotStatusCache[xpathPrefix + "is-booted-now"] = (std::get<std::string>(pit->second) == "booted" ? "true" : "false");
203 } else {
204 m_log->warn("RAUC didn't provide 'state' property for slot '{}'.", slotName);
205 }
206 if (auto pit = props.find("boot-status"); pit != props.end()) {
207 m_slotStatusCache[xpathPrefix + "is-healthy"] = (std::get<std::string>(pit->second) == "good" ? "true" : "false");
208 } else {
209 m_log->warn("RAUC didn't provide 'boot-status' property for slot '{}'.", slotName);
210 }
Jan Kundráte55b56b2022-07-13 12:51:44 +0200211 m_slotStatusCache[xpathPrefix + "will-boot-next"] = (slotName == primarySlot ? "true" : "false");
Jan Kundrát58b39ae2022-07-08 18:54:10 +0200212
Tomáš Pecka92b14e42021-01-27 17:33:11 +0100213 }
214 }
215
216 return lck;
217}
Tomáš Peckacb7a5f82021-01-20 15:12:00 +0100218}