blob: 4d190f9a3277a3e7a1ee2fdfcba32f8a0cd21e49 [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
30namespace internal
31{
32/** Construct the systemd service name */
33std::string getUpdateService(const std::string& psuInventoryPath,
34 const std::string& versionId)
35{
36 fs::path imagePath(IMG_DIR);
37 imagePath /= versionId;
38
39 // The systemd unit shall be escaped
40 std::string args = psuInventoryPath;
41 args += "\\x20";
42 args += imagePath;
43 std::replace(args.begin(), args.end(), '/', '-');
44
45 std::string service = PSU_UPDATE_SERVICE;
46 auto p = service.find('@');
47 assert(p != std::string::npos);
48 service.insert(p + 1, args);
49 return service;
50}
51
52} // namespace internal
Lei YU01539e72019-07-31 10:57:38 +080053auto Activation::activation(Activations value) -> Activations
54{
Lei YU12c9f4c2019-09-11 15:08:15 +080055 if (value == Status::Activating)
56 {
Lei YUff83c2a2019-09-12 13:55:18 +080057 value = startActivation();
Lei YU12c9f4c2019-09-11 15:08:15 +080058 }
59 else
60 {
Lei YU81c67722019-09-11 16:47:29 +080061 activationBlocksTransition.reset();
Lei YU90c8a8b2019-09-11 17:20:03 +080062 activationProgress.reset();
Lei YU12c9f4c2019-09-11 15:08:15 +080063 }
64
65 return SoftwareActivation::activation(value);
Lei YU01539e72019-07-31 10:57:38 +080066}
67
68auto Activation::requestedActivation(RequestedActivations value)
69 -> RequestedActivations
70{
Lei YU12c9f4c2019-09-11 15:08:15 +080071 if ((value == SoftwareActivation::RequestedActivations::Active) &&
72 (SoftwareActivation::requestedActivation() !=
73 SoftwareActivation::RequestedActivations::Active))
74 {
75 if ((activation() == Status::Ready) || (activation() == Status::Failed))
76 {
77 activation(Status::Activating);
78 }
79 }
80 return SoftwareActivation::requestedActivation(value);
81}
82
83void Activation::unitStateChange(sdbusplus::message::message& msg)
84{
85 uint32_t newStateID{};
86 sdbusplus::message::object_path newStateObjPath;
87 std::string newStateUnit{};
88 std::string newStateResult{};
89
90 // Read the msg and populate each variable
91 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
92
93 if (newStateUnit == psuUpdateUnit)
94 {
95 if (newStateResult == "done")
96 {
Lei YUff83c2a2019-09-12 13:55:18 +080097 onUpdateDone();
Lei YU12c9f4c2019-09-11 15:08:15 +080098 }
99 if (newStateResult == "failed" || newStateResult == "dependency")
100 {
Lei YUff83c2a2019-09-12 13:55:18 +0800101 onUpdateFailed();
Lei YU12c9f4c2019-09-11 15:08:15 +0800102 }
103 }
104}
105
Lei YUff83c2a2019-09-12 13:55:18 +0800106bool Activation::doUpdate(const std::string& psuInventoryPath)
107{
Lei YU7f2a2152019-09-16 16:50:18 +0800108 currentUpdatingPsu = psuInventoryPath;
109 psuUpdateUnit = internal::getUpdateService(currentUpdatingPsu, versionId);
Lei YUff83c2a2019-09-12 13:55:18 +0800110 try
111 {
112 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
113 SYSTEMD_INTERFACE, "StartUnit");
114 method.append(psuUpdateUnit, "replace");
115 bus.call_noreply(method);
116 return true;
117 }
118 catch (const SdBusError& e)
119 {
120 log<level::ERR>("Error staring service", entry("ERROR=%s", e.what()));
121 onUpdateFailed();
122 return false;
123 }
124}
125
126bool Activation::doUpdate()
127{
128 // When the queue is empty, all updates are done
129 if (psuQueue.empty())
130 {
131 finishActivation();
132 return true;
133 }
134
135 // Do the update on a PSU
136 const auto& psu = psuQueue.front();
137 return doUpdate(psu);
138}
139
140void Activation::onUpdateDone()
141{
142 auto progress = activationProgress->progress() + progressStep;
143 activationProgress->progress(progress);
144
Lei YU7f2a2152019-09-16 16:50:18 +0800145 // Update the activation association
146 auto assocs = associations();
147 assocs.emplace_back(ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION,
148 currentUpdatingPsu);
149 currentUpdatingPsu.clear();
150 associations(assocs);
151
Lei YUff83c2a2019-09-12 13:55:18 +0800152 psuQueue.pop();
153 doUpdate(); // Update the next psu
154}
155
156void Activation::onUpdateFailed()
157{
158 // TODO: report an event
159 log<level::ERR>("Failed to udpate PSU",
160 entry("PSU=%s", psuQueue.front().c_str()));
161 std::queue<std::string>().swap(psuQueue); // Clear the queue
162 activation(Status::Failed);
163}
164
165Activation::Status Activation::startActivation()
Lei YU12c9f4c2019-09-11 15:08:15 +0800166{
Lei YU90c8a8b2019-09-11 17:20:03 +0800167 if (!activationProgress)
168 {
Lei YU99301372019-09-29 16:27:12 +0800169 activationProgress = std::make_unique<ActivationProgress>(bus, objPath);
Lei YU90c8a8b2019-09-11 17:20:03 +0800170 }
Lei YU81c67722019-09-11 16:47:29 +0800171 if (!activationBlocksTransition)
172 {
173 activationBlocksTransition =
Lei YU99301372019-09-29 16:27:12 +0800174 std::make_unique<ActivationBlocksTransition>(bus, objPath);
Lei YU81c67722019-09-11 16:47:29 +0800175 }
176
Lei YU12c9f4c2019-09-11 15:08:15 +0800177 auto psuPaths = utils::getPSUInventoryPath(bus);
178 if (psuPaths.empty())
179 {
Lei YUff83c2a2019-09-12 13:55:18 +0800180 log<level::WARNING>("No PSU inventory found");
181 return Status::Failed;
Lei YU12c9f4c2019-09-11 15:08:15 +0800182 }
183
Lei YUff83c2a2019-09-12 13:55:18 +0800184 for (const auto& p : psuPaths)
185 {
Lei YU9edb7332019-09-19 14:46:19 +0800186 if (isCompatible(p))
187 {
188 psuQueue.push(p);
189 }
190 else
191 {
192 log<level::NOTICE>("PSU not compatible",
193 entry("PSU=%s", p.c_str()));
194 }
195 }
196
197 if (psuQueue.empty())
198 {
199 log<level::ERR>("No PSU compatible with the software");
200 return Status::Failed;
Lei YUff83c2a2019-09-12 13:55:18 +0800201 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800202
Lei YUff83c2a2019-09-12 13:55:18 +0800203 // The progress to be increased for each successful update of PSU
204 // E.g. in case we have 4 PSUs:
205 // 1. Initial progrss is 10
206 // 2. Add 20 after each update is done, so we will see progress to be 30,
207 // 50, 70, 90
208 // 3. When all PSUs are updated, it will be 100 and the interface is
209 // removed.
210 progressStep = 80 / psuQueue.size();
211 if (doUpdate())
212 {
213 activationProgress->progress(10);
214 return Status::Activating;
215 }
216 else
217 {
218 return Status::Failed;
219 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800220}
221
222void Activation::finishActivation()
223{
Lei YU2e0e2de2019-09-26 16:42:23 +0800224 storeImage();
Lei YU90c8a8b2019-09-11 17:20:03 +0800225 activationProgress->progress(100);
Lei YU81c67722019-09-11 16:47:29 +0800226
Lei YU12c9f4c2019-09-11 15:08:15 +0800227 // TODO: delete the old software object
Lei YUd0bbfa92019-09-11 16:10:54 +0800228 deleteImageManagerObject();
Lei YU7f2a2152019-09-16 16:50:18 +0800229
Lei YU99301372019-09-29 16:27:12 +0800230 associationInterface->createActiveAssociation(objPath);
231 associationInterface->addFunctionalAssociation(objPath);
Lei YU7f2a2152019-09-16 16:50:18 +0800232
Lei YU12c9f4c2019-09-11 15:08:15 +0800233 activation(Status::Active);
Lei YU01539e72019-07-31 10:57:38 +0800234}
235
Lei YUd0bbfa92019-09-11 16:10:54 +0800236void Activation::deleteImageManagerObject()
237{
238 // Get the Delete object for <versionID> inside image_manager
239 constexpr auto versionServiceStr = "xyz.openbmc_project.Software.Version";
240 constexpr auto deleteInterface = "xyz.openbmc_project.Object.Delete";
241 std::string versionService;
Lei YU99301372019-09-29 16:27:12 +0800242 auto services = utils::getServices(bus, objPath.c_str(), deleteInterface);
Lei YUd0bbfa92019-09-11 16:10:54 +0800243
244 // We need to find the phosphor-version-software-manager's version service
245 // to invoke the delete interface
246 for (const auto& service : services)
247 {
248 if (service.find(versionServiceStr) != std::string::npos)
249 {
250 versionService = service;
251 break;
252 }
253 }
254 if (versionService.empty())
255 {
256 log<level::ERR>("Error finding version service");
257 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 }
267 catch (const SdBusError& e)
268 {
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
301 auto src = fs::path(IMG_DIR) / versionId;
302 auto dst = fs::path(IMG_DIR_PERSIST) / model;
303 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 YU01539e72019-07-31 10:57:38 +0800318} // namespace updater
319} // namespace software
320} // namespace phosphor