blob: 9112c3f325319498ae2f956535b4bd68feaeee2f [file] [log] [blame]
Lei YU12c9f4c2019-09-11 15:08:15 +08001#include "config.h"
2
Lei YU01539e72019-07-31 10:57:38 +08003#include "activation.hpp"
4
Lei YU12c9f4c2019-09-11 15:08:15 +08005#include "utils.hpp"
6
7#include <cassert>
8#include <filesystem>
Lei YUd0bbfa92019-09-11 16:10:54 +08009#include <phosphor-logging/elog-errors.hpp>
10#include <phosphor-logging/log.hpp>
Lei YU12c9f4c2019-09-11 15:08:15 +080011
Lei YU01539e72019-07-31 10:57:38 +080012namespace phosphor
13{
14namespace software
15{
16namespace updater
17{
18
Lei YU12c9f4c2019-09-11 15:08:15 +080019constexpr auto SYSTEMD_BUSNAME = "org.freedesktop.systemd1";
20constexpr auto SYSTEMD_PATH = "/org/freedesktop/systemd1";
21constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
22
23namespace fs = std::filesystem;
Lei YU01539e72019-07-31 10:57:38 +080024namespace softwareServer = sdbusplus::xyz::openbmc_project::Software::server;
25
Lei YUd0bbfa92019-09-11 16:10:54 +080026using namespace phosphor::logging;
Lei YU12c9f4c2019-09-11 15:08:15 +080027using SoftwareActivation = softwareServer::Activation;
28
Lei YU01539e72019-07-31 10:57:38 +080029auto Activation::activation(Activations value) -> Activations
30{
Lei YU12c9f4c2019-09-11 15:08:15 +080031 if (value == Status::Activating)
32 {
Lei YUff83c2a2019-09-12 13:55:18 +080033 value = startActivation();
Lei YU12c9f4c2019-09-11 15:08:15 +080034 }
35 else
36 {
Lei YU81c67722019-09-11 16:47:29 +080037 activationBlocksTransition.reset();
Lei YU90c8a8b2019-09-11 17:20:03 +080038 activationProgress.reset();
Lei YU12c9f4c2019-09-11 15:08:15 +080039 }
40
41 return SoftwareActivation::activation(value);
Lei YU01539e72019-07-31 10:57:38 +080042}
43
44auto Activation::requestedActivation(RequestedActivations value)
45 -> RequestedActivations
46{
Lei YU12c9f4c2019-09-11 15:08:15 +080047 if ((value == SoftwareActivation::RequestedActivations::Active) &&
48 (SoftwareActivation::requestedActivation() !=
49 SoftwareActivation::RequestedActivations::Active))
50 {
Lei YU63f9e712019-10-12 15:16:55 +080051 // PSU image could be activated even when it's in active,
52 // e.g. in case a PSU is replaced and has a older image, it will be
53 // updated with the running PSU image that is stored in BMC.
54 if ((activation() == Status::Ready) ||
55 (activation() == Status::Failed) || activation() == Status::Active)
Lei YU12c9f4c2019-09-11 15:08:15 +080056 {
57 activation(Status::Activating);
58 }
59 }
60 return SoftwareActivation::requestedActivation(value);
61}
62
Patrick Williams374fae52022-07-22 19:26:55 -050063void Activation::unitStateChange(sdbusplus::message_t& msg)
Lei YU12c9f4c2019-09-11 15:08:15 +080064{
65 uint32_t newStateID{};
66 sdbusplus::message::object_path newStateObjPath;
67 std::string newStateUnit{};
68 std::string newStateResult{};
69
70 // Read the msg and populate each variable
71 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
72
73 if (newStateUnit == psuUpdateUnit)
74 {
75 if (newStateResult == "done")
76 {
Lei YUff83c2a2019-09-12 13:55:18 +080077 onUpdateDone();
Lei YU12c9f4c2019-09-11 15:08:15 +080078 }
79 if (newStateResult == "failed" || newStateResult == "dependency")
80 {
Lei YUff83c2a2019-09-12 13:55:18 +080081 onUpdateFailed();
Lei YU12c9f4c2019-09-11 15:08:15 +080082 }
83 }
84}
85
Lei YUff83c2a2019-09-12 13:55:18 +080086bool Activation::doUpdate(const std::string& psuInventoryPath)
87{
Lei YU7f2a2152019-09-16 16:50:18 +080088 currentUpdatingPsu = psuInventoryPath;
Lei YUe8945ea2019-09-29 17:25:31 +080089 psuUpdateUnit = getUpdateService(currentUpdatingPsu);
Lei YUff83c2a2019-09-12 13:55:18 +080090 try
91 {
92 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
93 SYSTEMD_INTERFACE, "StartUnit");
94 method.append(psuUpdateUnit, "replace");
95 bus.call_noreply(method);
96 return true;
97 }
Patrick Williams374fae52022-07-22 19:26:55 -050098 catch (const sdbusplus::exception_t& e)
Lei YUff83c2a2019-09-12 13:55:18 +080099 {
100 log<level::ERR>("Error staring service", entry("ERROR=%s", e.what()));
101 onUpdateFailed();
102 return false;
103 }
104}
105
106bool Activation::doUpdate()
107{
108 // When the queue is empty, all updates are done
109 if (psuQueue.empty())
110 {
111 finishActivation();
112 return true;
113 }
114
115 // Do the update on a PSU
116 const auto& psu = psuQueue.front();
117 return doUpdate(psu);
118}
119
120void Activation::onUpdateDone()
121{
122 auto progress = activationProgress->progress() + progressStep;
123 activationProgress->progress(progress);
124
Lei YU7f2a2152019-09-16 16:50:18 +0800125 // Update the activation association
126 auto assocs = associations();
127 assocs.emplace_back(ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION,
128 currentUpdatingPsu);
Lei YU7f2a2152019-09-16 16:50:18 +0800129 associations(assocs);
130
Lei YUffb36532019-10-15 13:55:24 +0800131 activationListener->onUpdateDone(versionId, currentUpdatingPsu);
132 currentUpdatingPsu.clear();
133
Lei YUff83c2a2019-09-12 13:55:18 +0800134 psuQueue.pop();
135 doUpdate(); // Update the next psu
136}
137
138void Activation::onUpdateFailed()
139{
140 // TODO: report an event
141 log<level::ERR>("Failed to udpate PSU",
142 entry("PSU=%s", psuQueue.front().c_str()));
143 std::queue<std::string>().swap(psuQueue); // Clear the queue
144 activation(Status::Failed);
145}
146
147Activation::Status Activation::startActivation()
Lei YU12c9f4c2019-09-11 15:08:15 +0800148{
Lei YU63f9e712019-10-12 15:16:55 +0800149 // Check if the activation has file path
150 if (path().empty())
151 {
152 log<level::WARNING>("No image for the activation, skipped",
153 entry("VERSION_ID=%s", versionId.c_str()));
154 return activation(); // Return the previous activation status
155 }
Lei YU81c67722019-09-11 16:47:29 +0800156
Lei YU12c9f4c2019-09-11 15:08:15 +0800157 auto psuPaths = utils::getPSUInventoryPath(bus);
158 if (psuPaths.empty())
159 {
Lei YUff83c2a2019-09-12 13:55:18 +0800160 log<level::WARNING>("No PSU inventory found");
161 return Status::Failed;
Lei YU12c9f4c2019-09-11 15:08:15 +0800162 }
163
Lei YUff83c2a2019-09-12 13:55:18 +0800164 for (const auto& p : psuPaths)
165 {
Lei YU9edb7332019-09-19 14:46:19 +0800166 if (isCompatible(p))
167 {
Lei YU63f9e712019-10-12 15:16:55 +0800168 if (utils::isAssociated(p, associations()))
169 {
170 log<level::NOTICE>("PSU already running the image, skipping",
171 entry("PSU=%s", p.c_str()));
172 continue;
173 }
Lei YU9edb7332019-09-19 14:46:19 +0800174 psuQueue.push(p);
175 }
176 else
177 {
178 log<level::NOTICE>("PSU not compatible",
179 entry("PSU=%s", p.c_str()));
180 }
181 }
182
183 if (psuQueue.empty())
184 {
Lei YU63f9e712019-10-12 15:16:55 +0800185 log<level::WARNING>("No PSU compatible with the software");
186 return activation(); // Return the previous activation status
Lei YUff83c2a2019-09-12 13:55:18 +0800187 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800188
Lei YU8afeee52019-10-21 15:25:35 +0800189 if (!activationProgress)
190 {
191 activationProgress = std::make_unique<ActivationProgress>(bus, objPath);
192 }
193 if (!activationBlocksTransition)
194 {
195 activationBlocksTransition =
196 std::make_unique<ActivationBlocksTransition>(bus, objPath);
197 }
198
Lei YUff83c2a2019-09-12 13:55:18 +0800199 // The progress to be increased for each successful update of PSU
200 // E.g. in case we have 4 PSUs:
201 // 1. Initial progrss is 10
202 // 2. Add 20 after each update is done, so we will see progress to be 30,
203 // 50, 70, 90
204 // 3. When all PSUs are updated, it will be 100 and the interface is
205 // removed.
206 progressStep = 80 / psuQueue.size();
207 if (doUpdate())
208 {
209 activationProgress->progress(10);
210 return Status::Activating;
211 }
212 else
213 {
214 return Status::Failed;
215 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800216}
217
218void Activation::finishActivation()
219{
Lei YU2e0e2de2019-09-26 16:42:23 +0800220 storeImage();
Lei YU90c8a8b2019-09-11 17:20:03 +0800221 activationProgress->progress(100);
Lei YU81c67722019-09-11 16:47:29 +0800222
Lei YUd0bbfa92019-09-11 16:10:54 +0800223 deleteImageManagerObject();
Lei YU7f2a2152019-09-16 16:50:18 +0800224
Lei YU99301372019-09-29 16:27:12 +0800225 associationInterface->createActiveAssociation(objPath);
226 associationInterface->addFunctionalAssociation(objPath);
Lei YUa8b966f2020-03-18 10:32:24 +0800227 associationInterface->addUpdateableAssociation(objPath);
Lei YU7f2a2152019-09-16 16:50:18 +0800228
Lei YU1517f5f2019-10-14 16:44:42 +0800229 // Reset RequestedActivations to none so that it could be activated in
230 // future
231 requestedActivation(SoftwareActivation::RequestedActivations::None);
Lei YU12c9f4c2019-09-11 15:08:15 +0800232 activation(Status::Active);
Lei YU01539e72019-07-31 10:57:38 +0800233}
234
Lei YUd0bbfa92019-09-11 16:10:54 +0800235void Activation::deleteImageManagerObject()
236{
237 // Get the Delete object for <versionID> inside image_manager
238 constexpr auto versionServiceStr = "xyz.openbmc_project.Software.Version";
239 constexpr auto deleteInterface = "xyz.openbmc_project.Object.Delete";
240 std::string versionService;
Lei YU99301372019-09-29 16:27:12 +0800241 auto services = utils::getServices(bus, objPath.c_str(), deleteInterface);
Lei YUd0bbfa92019-09-11 16:10:54 +0800242
243 // We need to find the phosphor-version-software-manager's version service
244 // to invoke the delete interface
245 for (const auto& service : services)
246 {
247 if (service.find(versionServiceStr) != std::string::npos)
248 {
249 versionService = service;
250 break;
251 }
252 }
253 if (versionService.empty())
254 {
Lei YUe8945ea2019-09-29 17:25:31 +0800255 // When updating a stored image, there is no version object created by
256 // "xyz.openbmc_project.Software.Version" service, so skip it.
Lei YUd0bbfa92019-09-11 16:10:54 +0800257 return;
258 }
259
260 // Call the Delete object for <versionID> inside image_manager
Lei YU99301372019-09-29 16:27:12 +0800261 auto method = bus.new_method_call(versionService.c_str(), objPath.c_str(),
Lei YUd0bbfa92019-09-11 16:10:54 +0800262 deleteInterface, "Delete");
263 try
264 {
265 bus.call(method);
266 }
Patrick Williams374fae52022-07-22 19:26:55 -0500267 catch (const sdbusplus::exception_t& e)
Lei YUd0bbfa92019-09-11 16:10:54 +0800268 {
269 log<level::ERR>("Error performing call to Delete object path",
270 entry("ERROR=%s", e.what()),
Lei YU99301372019-09-29 16:27:12 +0800271 entry("PATH=%s", objPath.c_str()));
Lei YUd0bbfa92019-09-11 16:10:54 +0800272 }
273}
274
Lei YU9edb7332019-09-19 14:46:19 +0800275bool Activation::isCompatible(const std::string& psuInventoryPath)
276{
277 auto service =
278 utils::getService(bus, psuInventoryPath.c_str(), ASSET_IFACE);
279 auto psuManufacturer = utils::getProperty<std::string>(
280 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE,
281 MANUFACTURER);
282 auto psuModel = utils::getProperty<std::string>(
283 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE, MODEL);
284 if (psuModel != model)
285 {
286 // The model shall match
287 return false;
288 }
289 if (!psuManufacturer.empty())
290 {
291 // If PSU inventory has manufacturer property, it shall match
292 return psuManufacturer == manufacturer;
293 }
294 return true;
295}
296
Lei YU2e0e2de2019-09-26 16:42:23 +0800297void Activation::storeImage()
298{
299 // Store image in persistent dir separated by model
300 // and only store the latest one by removing old ones
Lei YUe8945ea2019-09-29 17:25:31 +0800301 auto src = path();
Lei YU2e0e2de2019-09-26 16:42:23 +0800302 auto dst = fs::path(IMG_DIR_PERSIST) / model;
Lei YUe8945ea2019-09-29 17:25:31 +0800303 if (src == dst)
304 {
305 // This happens when updating an stored image, no need to store it again
306 return;
307 }
Lei YU2e0e2de2019-09-26 16:42:23 +0800308 try
309 {
310 fs::remove_all(dst);
311 fs::create_directories(dst);
312 fs::copy(src, dst);
313 path(dst.string()); // Update the FilePath interface
314 }
315 catch (const fs::filesystem_error& e)
316 {
317 log<level::ERR>("Error storing PSU image", entry("ERROR=%s", e.what()),
318 entry("SRC=%s", src.c_str()),
319 entry("DST=%s", dst.c_str()));
320 }
321}
322
Lei YUe8945ea2019-09-29 17:25:31 +0800323std::string Activation::getUpdateService(const std::string& psuInventoryPath)
324{
325 fs::path imagePath(path());
326
327 // The systemd unit shall be escaped
328 std::string args = psuInventoryPath;
329 args += "\\x20";
330 args += imagePath;
331 std::replace(args.begin(), args.end(), '/', '-');
332
333 std::string service = PSU_UPDATE_SERVICE;
334 auto p = service.find('@');
335 assert(p != std::string::npos);
336 service.insert(p + 1, args);
337 return service;
338}
339
Lei YU8afeee52019-10-21 15:25:35 +0800340void ActivationBlocksTransition::enableRebootGuard()
341{
342 log<level::INFO>("PSU image activating - BMC reboots are disabled.");
343
344 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
345 SYSTEMD_INTERFACE, "StartUnit");
346 method.append("reboot-guard-enable.service", "replace");
347 bus.call_noreply_noerror(method);
348}
349
350void ActivationBlocksTransition::disableRebootGuard()
351{
352 log<level::INFO>("PSU activation has ended - BMC reboots are re-enabled.");
353
354 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
355 SYSTEMD_INTERFACE, "StartUnit");
356 method.append("reboot-guard-disable.service", "replace");
357 bus.call_noreply_noerror(method);
358}
359
Lei YU01539e72019-07-31 10:57:38 +0800360} // namespace updater
361} // namespace software
362} // namespace phosphor