blob: 680cdd9d9b29d6505d9624c185aaa7eb26db774b [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{
Lei YU58c26e32019-09-27 17:52:06 +080036 // TODO: get image path from the related version
37 // because it could be in either IMG_DIR, or IMG_DIR_PERSIST, or
38 // IMG_DIR_BUILTIN
Lei YU12c9f4c2019-09-11 15:08:15 +080039 fs::path imagePath(IMG_DIR);
40 imagePath /= versionId;
41
42 // The systemd unit shall be escaped
43 std::string args = psuInventoryPath;
44 args += "\\x20";
45 args += imagePath;
46 std::replace(args.begin(), args.end(), '/', '-');
47
48 std::string service = PSU_UPDATE_SERVICE;
49 auto p = service.find('@');
50 assert(p != std::string::npos);
51 service.insert(p + 1, args);
52 return service;
53}
54
55} // namespace internal
Lei YU01539e72019-07-31 10:57:38 +080056auto Activation::activation(Activations value) -> Activations
57{
Lei YU12c9f4c2019-09-11 15:08:15 +080058 if (value == Status::Activating)
59 {
Lei YUff83c2a2019-09-12 13:55:18 +080060 value = startActivation();
Lei YU12c9f4c2019-09-11 15:08:15 +080061 }
62 else
63 {
Lei YU81c67722019-09-11 16:47:29 +080064 activationBlocksTransition.reset();
Lei YU90c8a8b2019-09-11 17:20:03 +080065 activationProgress.reset();
Lei YU12c9f4c2019-09-11 15:08:15 +080066 }
67
68 return SoftwareActivation::activation(value);
Lei YU01539e72019-07-31 10:57:38 +080069}
70
71auto Activation::requestedActivation(RequestedActivations value)
72 -> RequestedActivations
73{
Lei YU12c9f4c2019-09-11 15:08:15 +080074 if ((value == SoftwareActivation::RequestedActivations::Active) &&
75 (SoftwareActivation::requestedActivation() !=
76 SoftwareActivation::RequestedActivations::Active))
77 {
78 if ((activation() == Status::Ready) || (activation() == Status::Failed))
79 {
80 activation(Status::Activating);
81 }
82 }
83 return SoftwareActivation::requestedActivation(value);
84}
85
86void Activation::unitStateChange(sdbusplus::message::message& msg)
87{
88 uint32_t newStateID{};
89 sdbusplus::message::object_path newStateObjPath;
90 std::string newStateUnit{};
91 std::string newStateResult{};
92
93 // Read the msg and populate each variable
94 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
95
96 if (newStateUnit == psuUpdateUnit)
97 {
98 if (newStateResult == "done")
99 {
Lei YUff83c2a2019-09-12 13:55:18 +0800100 onUpdateDone();
Lei YU12c9f4c2019-09-11 15:08:15 +0800101 }
102 if (newStateResult == "failed" || newStateResult == "dependency")
103 {
Lei YUff83c2a2019-09-12 13:55:18 +0800104 onUpdateFailed();
Lei YU12c9f4c2019-09-11 15:08:15 +0800105 }
106 }
107}
108
Lei YUff83c2a2019-09-12 13:55:18 +0800109bool Activation::doUpdate(const std::string& psuInventoryPath)
110{
Lei YU7f2a2152019-09-16 16:50:18 +0800111 currentUpdatingPsu = psuInventoryPath;
112 psuUpdateUnit = internal::getUpdateService(currentUpdatingPsu, versionId);
Lei YUff83c2a2019-09-12 13:55:18 +0800113 try
114 {
115 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
116 SYSTEMD_INTERFACE, "StartUnit");
117 method.append(psuUpdateUnit, "replace");
118 bus.call_noreply(method);
119 return true;
120 }
121 catch (const SdBusError& e)
122 {
123 log<level::ERR>("Error staring service", entry("ERROR=%s", e.what()));
124 onUpdateFailed();
125 return false;
126 }
127}
128
129bool Activation::doUpdate()
130{
131 // When the queue is empty, all updates are done
132 if (psuQueue.empty())
133 {
134 finishActivation();
135 return true;
136 }
137
138 // Do the update on a PSU
139 const auto& psu = psuQueue.front();
140 return doUpdate(psu);
141}
142
143void Activation::onUpdateDone()
144{
145 auto progress = activationProgress->progress() + progressStep;
146 activationProgress->progress(progress);
147
Lei YU7f2a2152019-09-16 16:50:18 +0800148 // Update the activation association
149 auto assocs = associations();
150 assocs.emplace_back(ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION,
151 currentUpdatingPsu);
152 currentUpdatingPsu.clear();
153 associations(assocs);
154
Lei YUff83c2a2019-09-12 13:55:18 +0800155 psuQueue.pop();
156 doUpdate(); // Update the next psu
157}
158
159void Activation::onUpdateFailed()
160{
161 // TODO: report an event
162 log<level::ERR>("Failed to udpate PSU",
163 entry("PSU=%s", psuQueue.front().c_str()));
164 std::queue<std::string>().swap(psuQueue); // Clear the queue
165 activation(Status::Failed);
166}
167
168Activation::Status Activation::startActivation()
Lei YU12c9f4c2019-09-11 15:08:15 +0800169{
Lei YU90c8a8b2019-09-11 17:20:03 +0800170 if (!activationProgress)
171 {
Lei YU99301372019-09-29 16:27:12 +0800172 activationProgress = std::make_unique<ActivationProgress>(bus, objPath);
Lei YU90c8a8b2019-09-11 17:20:03 +0800173 }
Lei YU81c67722019-09-11 16:47:29 +0800174 if (!activationBlocksTransition)
175 {
176 activationBlocksTransition =
Lei YU99301372019-09-29 16:27:12 +0800177 std::make_unique<ActivationBlocksTransition>(bus, objPath);
Lei YU81c67722019-09-11 16:47:29 +0800178 }
179
Lei YU12c9f4c2019-09-11 15:08:15 +0800180 auto psuPaths = utils::getPSUInventoryPath(bus);
181 if (psuPaths.empty())
182 {
Lei YUff83c2a2019-09-12 13:55:18 +0800183 log<level::WARNING>("No PSU inventory found");
184 return Status::Failed;
Lei YU12c9f4c2019-09-11 15:08:15 +0800185 }
186
Lei YUff83c2a2019-09-12 13:55:18 +0800187 for (const auto& p : psuPaths)
188 {
Lei YU9edb7332019-09-19 14:46:19 +0800189 if (isCompatible(p))
190 {
191 psuQueue.push(p);
192 }
193 else
194 {
195 log<level::NOTICE>("PSU not compatible",
196 entry("PSU=%s", p.c_str()));
197 }
198 }
199
200 if (psuQueue.empty())
201 {
202 log<level::ERR>("No PSU compatible with the software");
203 return Status::Failed;
Lei YUff83c2a2019-09-12 13:55:18 +0800204 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800205
Lei YUff83c2a2019-09-12 13:55:18 +0800206 // The progress to be increased for each successful update of PSU
207 // E.g. in case we have 4 PSUs:
208 // 1. Initial progrss is 10
209 // 2. Add 20 after each update is done, so we will see progress to be 30,
210 // 50, 70, 90
211 // 3. When all PSUs are updated, it will be 100 and the interface is
212 // removed.
213 progressStep = 80 / psuQueue.size();
214 if (doUpdate())
215 {
216 activationProgress->progress(10);
217 return Status::Activating;
218 }
219 else
220 {
221 return Status::Failed;
222 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800223}
224
225void Activation::finishActivation()
226{
Lei YU2e0e2de2019-09-26 16:42:23 +0800227 storeImage();
Lei YU90c8a8b2019-09-11 17:20:03 +0800228 activationProgress->progress(100);
Lei YU81c67722019-09-11 16:47:29 +0800229
Lei YU12c9f4c2019-09-11 15:08:15 +0800230 // TODO: delete the old software object
Lei YUd0bbfa92019-09-11 16:10:54 +0800231 deleteImageManagerObject();
Lei YU7f2a2152019-09-16 16:50:18 +0800232
Lei YU99301372019-09-29 16:27:12 +0800233 associationInterface->createActiveAssociation(objPath);
234 associationInterface->addFunctionalAssociation(objPath);
Lei YU7f2a2152019-09-16 16:50:18 +0800235
Lei YU12c9f4c2019-09-11 15:08:15 +0800236 activation(Status::Active);
Lei YU01539e72019-07-31 10:57:38 +0800237}
238
Lei YUd0bbfa92019-09-11 16:10:54 +0800239void Activation::deleteImageManagerObject()
240{
241 // Get the Delete object for <versionID> inside image_manager
242 constexpr auto versionServiceStr = "xyz.openbmc_project.Software.Version";
243 constexpr auto deleteInterface = "xyz.openbmc_project.Object.Delete";
244 std::string versionService;
Lei YU99301372019-09-29 16:27:12 +0800245 auto services = utils::getServices(bus, objPath.c_str(), deleteInterface);
Lei YUd0bbfa92019-09-11 16:10:54 +0800246
247 // We need to find the phosphor-version-software-manager's version service
248 // to invoke the delete interface
249 for (const auto& service : services)
250 {
251 if (service.find(versionServiceStr) != std::string::npos)
252 {
253 versionService = service;
254 break;
255 }
256 }
257 if (versionService.empty())
258 {
259 log<level::ERR>("Error finding version service");
260 return;
261 }
262
263 // Call the Delete object for <versionID> inside image_manager
Lei YU99301372019-09-29 16:27:12 +0800264 auto method = bus.new_method_call(versionService.c_str(), objPath.c_str(),
Lei YUd0bbfa92019-09-11 16:10:54 +0800265 deleteInterface, "Delete");
266 try
267 {
268 bus.call(method);
269 }
270 catch (const SdBusError& e)
271 {
272 log<level::ERR>("Error performing call to Delete object path",
273 entry("ERROR=%s", e.what()),
Lei YU99301372019-09-29 16:27:12 +0800274 entry("PATH=%s", objPath.c_str()));
Lei YUd0bbfa92019-09-11 16:10:54 +0800275 }
276}
277
Lei YU9edb7332019-09-19 14:46:19 +0800278bool Activation::isCompatible(const std::string& psuInventoryPath)
279{
280 auto service =
281 utils::getService(bus, psuInventoryPath.c_str(), ASSET_IFACE);
282 auto psuManufacturer = utils::getProperty<std::string>(
283 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE,
284 MANUFACTURER);
285 auto psuModel = utils::getProperty<std::string>(
286 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE, MODEL);
287 if (psuModel != model)
288 {
289 // The model shall match
290 return false;
291 }
292 if (!psuManufacturer.empty())
293 {
294 // If PSU inventory has manufacturer property, it shall match
295 return psuManufacturer == manufacturer;
296 }
297 return true;
298}
299
Lei YU2e0e2de2019-09-26 16:42:23 +0800300void Activation::storeImage()
301{
302 // Store image in persistent dir separated by model
303 // and only store the latest one by removing old ones
304 auto src = fs::path(IMG_DIR) / versionId;
305 auto dst = fs::path(IMG_DIR_PERSIST) / model;
306 try
307 {
308 fs::remove_all(dst);
309 fs::create_directories(dst);
310 fs::copy(src, dst);
311 path(dst.string()); // Update the FilePath interface
312 }
313 catch (const fs::filesystem_error& e)
314 {
315 log<level::ERR>("Error storing PSU image", entry("ERROR=%s", e.what()),
316 entry("SRC=%s", src.c_str()),
317 entry("DST=%s", dst.c_str()));
318 }
319}
320
Lei YU01539e72019-07-31 10:57:38 +0800321} // namespace updater
322} // namespace software
323} // namespace phosphor