blob: cab9acd638a648dcb047a069f1cea1dbceecca21 [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;
27using sdbusplus::exception::SdBusError;
Lei YU12c9f4c2019-09-11 15:08:15 +080028using SoftwareActivation = softwareServer::Activation;
29
Lei YU01539e72019-07-31 10:57:38 +080030auto Activation::activation(Activations value) -> Activations
31{
Lei YU12c9f4c2019-09-11 15:08:15 +080032 if (value == Status::Activating)
33 {
Lei YUff83c2a2019-09-12 13:55:18 +080034 value = startActivation();
Lei YU12c9f4c2019-09-11 15:08:15 +080035 }
36 else
37 {
Lei YU81c67722019-09-11 16:47:29 +080038 activationBlocksTransition.reset();
Lei YU90c8a8b2019-09-11 17:20:03 +080039 activationProgress.reset();
Lei YU12c9f4c2019-09-11 15:08:15 +080040 }
41
42 return SoftwareActivation::activation(value);
Lei YU01539e72019-07-31 10:57:38 +080043}
44
45auto Activation::requestedActivation(RequestedActivations value)
46 -> RequestedActivations
47{
Lei YU12c9f4c2019-09-11 15:08:15 +080048 if ((value == SoftwareActivation::RequestedActivations::Active) &&
49 (SoftwareActivation::requestedActivation() !=
50 SoftwareActivation::RequestedActivations::Active))
51 {
Lei YU63f9e712019-10-12 15:16:55 +080052 // PSU image could be activated even when it's in active,
53 // e.g. in case a PSU is replaced and has a older image, it will be
54 // updated with the running PSU image that is stored in BMC.
55 if ((activation() == Status::Ready) ||
56 (activation() == Status::Failed) || activation() == Status::Active)
Lei YU12c9f4c2019-09-11 15:08:15 +080057 {
58 activation(Status::Activating);
59 }
60 }
61 return SoftwareActivation::requestedActivation(value);
62}
63
64void Activation::unitStateChange(sdbusplus::message::message& msg)
65{
66 uint32_t newStateID{};
67 sdbusplus::message::object_path newStateObjPath;
68 std::string newStateUnit{};
69 std::string newStateResult{};
70
71 // Read the msg and populate each variable
72 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
73
74 if (newStateUnit == psuUpdateUnit)
75 {
76 if (newStateResult == "done")
77 {
Lei YUff83c2a2019-09-12 13:55:18 +080078 onUpdateDone();
Lei YU12c9f4c2019-09-11 15:08:15 +080079 }
80 if (newStateResult == "failed" || newStateResult == "dependency")
81 {
Lei YUff83c2a2019-09-12 13:55:18 +080082 onUpdateFailed();
Lei YU12c9f4c2019-09-11 15:08:15 +080083 }
84 }
85}
86
Lei YUff83c2a2019-09-12 13:55:18 +080087bool Activation::doUpdate(const std::string& psuInventoryPath)
88{
Lei YU7f2a2152019-09-16 16:50:18 +080089 currentUpdatingPsu = psuInventoryPath;
Lei YUe8945ea2019-09-29 17:25:31 +080090 psuUpdateUnit = getUpdateService(currentUpdatingPsu);
Lei YUff83c2a2019-09-12 13:55:18 +080091 try
92 {
93 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
94 SYSTEMD_INTERFACE, "StartUnit");
95 method.append(psuUpdateUnit, "replace");
96 bus.call_noreply(method);
97 return true;
98 }
99 catch (const SdBusError& e)
100 {
101 log<level::ERR>("Error staring service", entry("ERROR=%s", e.what()));
102 onUpdateFailed();
103 return false;
104 }
105}
106
107bool Activation::doUpdate()
108{
109 // When the queue is empty, all updates are done
110 if (psuQueue.empty())
111 {
112 finishActivation();
113 return true;
114 }
115
116 // Do the update on a PSU
117 const auto& psu = psuQueue.front();
118 return doUpdate(psu);
119}
120
121void Activation::onUpdateDone()
122{
123 auto progress = activationProgress->progress() + progressStep;
124 activationProgress->progress(progress);
125
Lei YU7f2a2152019-09-16 16:50:18 +0800126 // Update the activation association
127 auto assocs = associations();
128 assocs.emplace_back(ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION,
129 currentUpdatingPsu);
130 currentUpdatingPsu.clear();
131 associations(assocs);
132
Lei YUff83c2a2019-09-12 13:55:18 +0800133 psuQueue.pop();
134 doUpdate(); // Update the next psu
135}
136
137void Activation::onUpdateFailed()
138{
139 // TODO: report an event
140 log<level::ERR>("Failed to udpate PSU",
141 entry("PSU=%s", psuQueue.front().c_str()));
142 std::queue<std::string>().swap(psuQueue); // Clear the queue
143 activation(Status::Failed);
144}
145
146Activation::Status Activation::startActivation()
Lei YU12c9f4c2019-09-11 15:08:15 +0800147{
Lei YU63f9e712019-10-12 15:16:55 +0800148 // Check if the activation has file path
149 if (path().empty())
150 {
151 log<level::WARNING>("No image for the activation, skipped",
152 entry("VERSION_ID=%s", versionId.c_str()));
153 return activation(); // Return the previous activation status
154 }
Lei YU90c8a8b2019-09-11 17:20:03 +0800155 if (!activationProgress)
156 {
Lei YU99301372019-09-29 16:27:12 +0800157 activationProgress = std::make_unique<ActivationProgress>(bus, objPath);
Lei YU90c8a8b2019-09-11 17:20:03 +0800158 }
Lei YU81c67722019-09-11 16:47:29 +0800159 if (!activationBlocksTransition)
160 {
161 activationBlocksTransition =
Lei YU99301372019-09-29 16:27:12 +0800162 std::make_unique<ActivationBlocksTransition>(bus, objPath);
Lei YU81c67722019-09-11 16:47:29 +0800163 }
164
Lei YU12c9f4c2019-09-11 15:08:15 +0800165 auto psuPaths = utils::getPSUInventoryPath(bus);
166 if (psuPaths.empty())
167 {
Lei YUff83c2a2019-09-12 13:55:18 +0800168 log<level::WARNING>("No PSU inventory found");
169 return Status::Failed;
Lei YU12c9f4c2019-09-11 15:08:15 +0800170 }
171
Lei YUff83c2a2019-09-12 13:55:18 +0800172 for (const auto& p : psuPaths)
173 {
Lei YU9edb7332019-09-19 14:46:19 +0800174 if (isCompatible(p))
175 {
Lei YU63f9e712019-10-12 15:16:55 +0800176 if (utils::isAssociated(p, associations()))
177 {
178 log<level::NOTICE>("PSU already running the image, skipping",
179 entry("PSU=%s", p.c_str()));
180 continue;
181 }
Lei YU9edb7332019-09-19 14:46:19 +0800182 psuQueue.push(p);
183 }
184 else
185 {
186 log<level::NOTICE>("PSU not compatible",
187 entry("PSU=%s", p.c_str()));
188 }
189 }
190
191 if (psuQueue.empty())
192 {
Lei YU63f9e712019-10-12 15:16:55 +0800193 log<level::WARNING>("No PSU compatible with the software");
194 return activation(); // Return the previous activation status
Lei YUff83c2a2019-09-12 13:55:18 +0800195 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800196
Lei YUff83c2a2019-09-12 13:55:18 +0800197 // The progress to be increased for each successful update of PSU
198 // E.g. in case we have 4 PSUs:
199 // 1. Initial progrss is 10
200 // 2. Add 20 after each update is done, so we will see progress to be 30,
201 // 50, 70, 90
202 // 3. When all PSUs are updated, it will be 100 and the interface is
203 // removed.
204 progressStep = 80 / psuQueue.size();
205 if (doUpdate())
206 {
207 activationProgress->progress(10);
208 return Status::Activating;
209 }
210 else
211 {
212 return Status::Failed;
213 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800214}
215
216void Activation::finishActivation()
217{
Lei YU2e0e2de2019-09-26 16:42:23 +0800218 storeImage();
Lei YU90c8a8b2019-09-11 17:20:03 +0800219 activationProgress->progress(100);
Lei YU81c67722019-09-11 16:47:29 +0800220
Lei YU12c9f4c2019-09-11 15:08:15 +0800221 // TODO: delete the old software object
Lei YUd0bbfa92019-09-11 16:10:54 +0800222 deleteImageManagerObject();
Lei YU7f2a2152019-09-16 16:50:18 +0800223
Lei YU99301372019-09-29 16:27:12 +0800224 associationInterface->createActiveAssociation(objPath);
225 associationInterface->addFunctionalAssociation(objPath);
Lei YU7f2a2152019-09-16 16:50:18 +0800226
Lei YU12c9f4c2019-09-11 15:08:15 +0800227 activation(Status::Active);
Lei YU01539e72019-07-31 10:57:38 +0800228}
229
Lei YUd0bbfa92019-09-11 16:10:54 +0800230void Activation::deleteImageManagerObject()
231{
232 // Get the Delete object for <versionID> inside image_manager
233 constexpr auto versionServiceStr = "xyz.openbmc_project.Software.Version";
234 constexpr auto deleteInterface = "xyz.openbmc_project.Object.Delete";
235 std::string versionService;
Lei YU99301372019-09-29 16:27:12 +0800236 auto services = utils::getServices(bus, objPath.c_str(), deleteInterface);
Lei YUd0bbfa92019-09-11 16:10:54 +0800237
238 // We need to find the phosphor-version-software-manager's version service
239 // to invoke the delete interface
240 for (const auto& service : services)
241 {
242 if (service.find(versionServiceStr) != std::string::npos)
243 {
244 versionService = service;
245 break;
246 }
247 }
248 if (versionService.empty())
249 {
Lei YUe8945ea2019-09-29 17:25:31 +0800250 // When updating a stored image, there is no version object created by
251 // "xyz.openbmc_project.Software.Version" service, so skip it.
Lei YUd0bbfa92019-09-11 16:10:54 +0800252 return;
253 }
254
255 // Call the Delete object for <versionID> inside image_manager
Lei YU99301372019-09-29 16:27:12 +0800256 auto method = bus.new_method_call(versionService.c_str(), objPath.c_str(),
Lei YUd0bbfa92019-09-11 16:10:54 +0800257 deleteInterface, "Delete");
258 try
259 {
260 bus.call(method);
261 }
262 catch (const SdBusError& e)
263 {
264 log<level::ERR>("Error performing call to Delete object path",
265 entry("ERROR=%s", e.what()),
Lei YU99301372019-09-29 16:27:12 +0800266 entry("PATH=%s", objPath.c_str()));
Lei YUd0bbfa92019-09-11 16:10:54 +0800267 }
268}
269
Lei YU9edb7332019-09-19 14:46:19 +0800270bool Activation::isCompatible(const std::string& psuInventoryPath)
271{
272 auto service =
273 utils::getService(bus, psuInventoryPath.c_str(), ASSET_IFACE);
274 auto psuManufacturer = utils::getProperty<std::string>(
275 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE,
276 MANUFACTURER);
277 auto psuModel = utils::getProperty<std::string>(
278 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE, MODEL);
279 if (psuModel != model)
280 {
281 // The model shall match
282 return false;
283 }
284 if (!psuManufacturer.empty())
285 {
286 // If PSU inventory has manufacturer property, it shall match
287 return psuManufacturer == manufacturer;
288 }
289 return true;
290}
291
Lei YU2e0e2de2019-09-26 16:42:23 +0800292void Activation::storeImage()
293{
294 // Store image in persistent dir separated by model
295 // and only store the latest one by removing old ones
Lei YUe8945ea2019-09-29 17:25:31 +0800296 auto src = path();
Lei YU2e0e2de2019-09-26 16:42:23 +0800297 auto dst = fs::path(IMG_DIR_PERSIST) / model;
Lei YUe8945ea2019-09-29 17:25:31 +0800298 if (src == dst)
299 {
300 // This happens when updating an stored image, no need to store it again
301 return;
302 }
Lei YU2e0e2de2019-09-26 16:42:23 +0800303 try
304 {
305 fs::remove_all(dst);
306 fs::create_directories(dst);
307 fs::copy(src, dst);
308 path(dst.string()); // Update the FilePath interface
309 }
310 catch (const fs::filesystem_error& e)
311 {
312 log<level::ERR>("Error storing PSU image", entry("ERROR=%s", e.what()),
313 entry("SRC=%s", src.c_str()),
314 entry("DST=%s", dst.c_str()));
315 }
316}
317
Lei YUe8945ea2019-09-29 17:25:31 +0800318std::string Activation::getUpdateService(const std::string& psuInventoryPath)
319{
320 fs::path imagePath(path());
321
322 // The systemd unit shall be escaped
323 std::string args = psuInventoryPath;
324 args += "\\x20";
325 args += imagePath;
326 std::replace(args.begin(), args.end(), '/', '-');
327
328 std::string service = PSU_UPDATE_SERVICE;
329 auto p = service.find('@');
330 assert(p != std::string::npos);
331 service.insert(p + 1, args);
332 return service;
333}
334
Lei YU01539e72019-07-31 10:57:38 +0800335} // namespace updater
336} // namespace software
337} // namespace phosphor