blob: efff61b50c2735d4427b6d2fbf4bc0c52de4c516 [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 {
52 if ((activation() == Status::Ready) || (activation() == Status::Failed))
53 {
54 activation(Status::Activating);
55 }
56 }
57 return SoftwareActivation::requestedActivation(value);
58}
59
60void Activation::unitStateChange(sdbusplus::message::message& msg)
61{
62 uint32_t newStateID{};
63 sdbusplus::message::object_path newStateObjPath;
64 std::string newStateUnit{};
65 std::string newStateResult{};
66
67 // Read the msg and populate each variable
68 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
69
70 if (newStateUnit == psuUpdateUnit)
71 {
72 if (newStateResult == "done")
73 {
Lei YUff83c2a2019-09-12 13:55:18 +080074 onUpdateDone();
Lei YU12c9f4c2019-09-11 15:08:15 +080075 }
76 if (newStateResult == "failed" || newStateResult == "dependency")
77 {
Lei YUff83c2a2019-09-12 13:55:18 +080078 onUpdateFailed();
Lei YU12c9f4c2019-09-11 15:08:15 +080079 }
80 }
81}
82
Lei YUff83c2a2019-09-12 13:55:18 +080083bool Activation::doUpdate(const std::string& psuInventoryPath)
84{
Lei YU7f2a2152019-09-16 16:50:18 +080085 currentUpdatingPsu = psuInventoryPath;
Lei YUe8945ea2019-09-29 17:25:31 +080086 psuUpdateUnit = getUpdateService(currentUpdatingPsu);
Lei YUff83c2a2019-09-12 13:55:18 +080087 try
88 {
89 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
90 SYSTEMD_INTERFACE, "StartUnit");
91 method.append(psuUpdateUnit, "replace");
92 bus.call_noreply(method);
93 return true;
94 }
95 catch (const SdBusError& e)
96 {
97 log<level::ERR>("Error staring service", entry("ERROR=%s", e.what()));
98 onUpdateFailed();
99 return false;
100 }
101}
102
103bool Activation::doUpdate()
104{
105 // When the queue is empty, all updates are done
106 if (psuQueue.empty())
107 {
108 finishActivation();
109 return true;
110 }
111
112 // Do the update on a PSU
113 const auto& psu = psuQueue.front();
114 return doUpdate(psu);
115}
116
117void Activation::onUpdateDone()
118{
119 auto progress = activationProgress->progress() + progressStep;
120 activationProgress->progress(progress);
121
Lei YU7f2a2152019-09-16 16:50:18 +0800122 // Update the activation association
123 auto assocs = associations();
124 assocs.emplace_back(ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION,
125 currentUpdatingPsu);
126 currentUpdatingPsu.clear();
127 associations(assocs);
128
Lei YUff83c2a2019-09-12 13:55:18 +0800129 psuQueue.pop();
130 doUpdate(); // Update the next psu
131}
132
133void Activation::onUpdateFailed()
134{
135 // TODO: report an event
136 log<level::ERR>("Failed to udpate PSU",
137 entry("PSU=%s", psuQueue.front().c_str()));
138 std::queue<std::string>().swap(psuQueue); // Clear the queue
139 activation(Status::Failed);
140}
141
142Activation::Status Activation::startActivation()
Lei YU12c9f4c2019-09-11 15:08:15 +0800143{
Lei YU90c8a8b2019-09-11 17:20:03 +0800144 if (!activationProgress)
145 {
Lei YU99301372019-09-29 16:27:12 +0800146 activationProgress = std::make_unique<ActivationProgress>(bus, objPath);
Lei YU90c8a8b2019-09-11 17:20:03 +0800147 }
Lei YU81c67722019-09-11 16:47:29 +0800148 if (!activationBlocksTransition)
149 {
150 activationBlocksTransition =
Lei YU99301372019-09-29 16:27:12 +0800151 std::make_unique<ActivationBlocksTransition>(bus, objPath);
Lei YU81c67722019-09-11 16:47:29 +0800152 }
153
Lei YU12c9f4c2019-09-11 15:08:15 +0800154 auto psuPaths = utils::getPSUInventoryPath(bus);
155 if (psuPaths.empty())
156 {
Lei YUff83c2a2019-09-12 13:55:18 +0800157 log<level::WARNING>("No PSU inventory found");
158 return Status::Failed;
Lei YU12c9f4c2019-09-11 15:08:15 +0800159 }
160
Lei YUff83c2a2019-09-12 13:55:18 +0800161 for (const auto& p : psuPaths)
162 {
Lei YU9edb7332019-09-19 14:46:19 +0800163 if (isCompatible(p))
164 {
165 psuQueue.push(p);
166 }
167 else
168 {
169 log<level::NOTICE>("PSU not compatible",
170 entry("PSU=%s", p.c_str()));
171 }
172 }
173
174 if (psuQueue.empty())
175 {
176 log<level::ERR>("No PSU compatible with the software");
177 return Status::Failed;
Lei YUff83c2a2019-09-12 13:55:18 +0800178 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800179
Lei YUff83c2a2019-09-12 13:55:18 +0800180 // The progress to be increased for each successful update of PSU
181 // E.g. in case we have 4 PSUs:
182 // 1. Initial progrss is 10
183 // 2. Add 20 after each update is done, so we will see progress to be 30,
184 // 50, 70, 90
185 // 3. When all PSUs are updated, it will be 100 and the interface is
186 // removed.
187 progressStep = 80 / psuQueue.size();
188 if (doUpdate())
189 {
190 activationProgress->progress(10);
191 return Status::Activating;
192 }
193 else
194 {
195 return Status::Failed;
196 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800197}
198
199void Activation::finishActivation()
200{
Lei YU2e0e2de2019-09-26 16:42:23 +0800201 storeImage();
Lei YU90c8a8b2019-09-11 17:20:03 +0800202 activationProgress->progress(100);
Lei YU81c67722019-09-11 16:47:29 +0800203
Lei YU12c9f4c2019-09-11 15:08:15 +0800204 // TODO: delete the old software object
Lei YUd0bbfa92019-09-11 16:10:54 +0800205 deleteImageManagerObject();
Lei YU7f2a2152019-09-16 16:50:18 +0800206
Lei YU99301372019-09-29 16:27:12 +0800207 associationInterface->createActiveAssociation(objPath);
208 associationInterface->addFunctionalAssociation(objPath);
Lei YU7f2a2152019-09-16 16:50:18 +0800209
Lei YU12c9f4c2019-09-11 15:08:15 +0800210 activation(Status::Active);
Lei YU01539e72019-07-31 10:57:38 +0800211}
212
Lei YUd0bbfa92019-09-11 16:10:54 +0800213void Activation::deleteImageManagerObject()
214{
215 // Get the Delete object for <versionID> inside image_manager
216 constexpr auto versionServiceStr = "xyz.openbmc_project.Software.Version";
217 constexpr auto deleteInterface = "xyz.openbmc_project.Object.Delete";
218 std::string versionService;
Lei YU99301372019-09-29 16:27:12 +0800219 auto services = utils::getServices(bus, objPath.c_str(), deleteInterface);
Lei YUd0bbfa92019-09-11 16:10:54 +0800220
221 // We need to find the phosphor-version-software-manager's version service
222 // to invoke the delete interface
223 for (const auto& service : services)
224 {
225 if (service.find(versionServiceStr) != std::string::npos)
226 {
227 versionService = service;
228 break;
229 }
230 }
231 if (versionService.empty())
232 {
Lei YUe8945ea2019-09-29 17:25:31 +0800233 // When updating a stored image, there is no version object created by
234 // "xyz.openbmc_project.Software.Version" service, so skip it.
Lei YUd0bbfa92019-09-11 16:10:54 +0800235 return;
236 }
237
238 // Call the Delete object for <versionID> inside image_manager
Lei YU99301372019-09-29 16:27:12 +0800239 auto method = bus.new_method_call(versionService.c_str(), objPath.c_str(),
Lei YUd0bbfa92019-09-11 16:10:54 +0800240 deleteInterface, "Delete");
241 try
242 {
243 bus.call(method);
244 }
245 catch (const SdBusError& e)
246 {
247 log<level::ERR>("Error performing call to Delete object path",
248 entry("ERROR=%s", e.what()),
Lei YU99301372019-09-29 16:27:12 +0800249 entry("PATH=%s", objPath.c_str()));
Lei YUd0bbfa92019-09-11 16:10:54 +0800250 }
251}
252
Lei YU9edb7332019-09-19 14:46:19 +0800253bool Activation::isCompatible(const std::string& psuInventoryPath)
254{
255 auto service =
256 utils::getService(bus, psuInventoryPath.c_str(), ASSET_IFACE);
257 auto psuManufacturer = utils::getProperty<std::string>(
258 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE,
259 MANUFACTURER);
260 auto psuModel = utils::getProperty<std::string>(
261 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE, MODEL);
262 if (psuModel != model)
263 {
264 // The model shall match
265 return false;
266 }
267 if (!psuManufacturer.empty())
268 {
269 // If PSU inventory has manufacturer property, it shall match
270 return psuManufacturer == manufacturer;
271 }
272 return true;
273}
274
Lei YU2e0e2de2019-09-26 16:42:23 +0800275void Activation::storeImage()
276{
277 // Store image in persistent dir separated by model
278 // and only store the latest one by removing old ones
Lei YUe8945ea2019-09-29 17:25:31 +0800279 auto src = path();
Lei YU2e0e2de2019-09-26 16:42:23 +0800280 auto dst = fs::path(IMG_DIR_PERSIST) / model;
Lei YUe8945ea2019-09-29 17:25:31 +0800281 if (src == dst)
282 {
283 // This happens when updating an stored image, no need to store it again
284 return;
285 }
Lei YU2e0e2de2019-09-26 16:42:23 +0800286 try
287 {
288 fs::remove_all(dst);
289 fs::create_directories(dst);
290 fs::copy(src, dst);
291 path(dst.string()); // Update the FilePath interface
292 }
293 catch (const fs::filesystem_error& e)
294 {
295 log<level::ERR>("Error storing PSU image", entry("ERROR=%s", e.what()),
296 entry("SRC=%s", src.c_str()),
297 entry("DST=%s", dst.c_str()));
298 }
299}
300
Lei YUe8945ea2019-09-29 17:25:31 +0800301std::string Activation::getUpdateService(const std::string& psuInventoryPath)
302{
303 fs::path imagePath(path());
304
305 // The systemd unit shall be escaped
306 std::string args = psuInventoryPath;
307 args += "\\x20";
308 args += imagePath;
309 std::replace(args.begin(), args.end(), '/', '-');
310
311 std::string service = PSU_UPDATE_SERVICE;
312 auto p = service.find('@');
313 assert(p != std::string::npos);
314 service.insert(p + 1, args);
315 return service;
316}
317
Lei YU01539e72019-07-31 10:57:38 +0800318} // namespace updater
319} // namespace software
320} // namespace phosphor