blob: acbc943b66dc9aa83f51a1e24ee57f8a4c9e0675 [file] [log] [blame]
Lei YU01539e72019-07-31 10:57:38 +08001#include "config.h"
2
3#include "item_updater.hpp"
4
Lei YUad90ad52019-08-06 11:19:28 +08005#include "utils.hpp"
6
Lei YU65207482019-10-11 16:39:36 +08007#include <cassert>
Lei YU01539e72019-07-31 10:57:38 +08008#include <filesystem>
9#include <phosphor-logging/elog-errors.hpp>
10#include <phosphor-logging/log.hpp>
11#include <xyz/openbmc_project/Common/error.hpp>
12
Lei YUfda15a32019-09-19 14:43:02 +080013namespace
14{
Lei YU58c26e32019-09-27 17:52:06 +080015constexpr auto MANIFEST_VERSION = "version";
16constexpr auto MANIFEST_EXTENDED_VERSION = "extended_version";
17} // namespace
Lei YUfda15a32019-09-19 14:43:02 +080018
Lei YU01539e72019-07-31 10:57:38 +080019namespace phosphor
20{
21namespace software
22{
23namespace updater
24{
25namespace server = sdbusplus::xyz::openbmc_project::Software::server;
Lei YU01539e72019-07-31 10:57:38 +080026
27using namespace sdbusplus::xyz::openbmc_project::Common::Error;
28using namespace phosphor::logging;
Lei YUad90ad52019-08-06 11:19:28 +080029using SVersion = server::Version;
30using VersionPurpose = SVersion::VersionPurpose;
Lei YU01539e72019-07-31 10:57:38 +080031
32void ItemUpdater::createActivation(sdbusplus::message::message& m)
33{
Lei YU01539e72019-07-31 10:57:38 +080034 namespace msg = sdbusplus::message;
35 namespace variant_ns = msg::variant_ns;
36
37 sdbusplus::message::object_path objPath;
38 std::map<std::string, std::map<std::string, msg::variant<std::string>>>
39 interfaces;
40 m.read(objPath, interfaces);
41
42 std::string path(std::move(objPath));
43 std::string filePath;
44 auto purpose = VersionPurpose::Unknown;
45 std::string version;
46
47 for (const auto& [interfaceName, propertyMap] : interfaces)
48 {
49 if (interfaceName == VERSION_IFACE)
50 {
51 for (const auto& [propertyName, propertyValue] : propertyMap)
52 {
53 if (propertyName == "Purpose")
54 {
55 // Only process the PSU images
56 auto value = SVersion::convertVersionPurposeFromString(
57 variant_ns::get<std::string>(propertyValue));
58
59 if (value == VersionPurpose::PSU)
60 {
61 purpose = value;
62 }
63 }
Lei YUf77189f2019-08-07 14:26:30 +080064 else if (propertyName == VERSION)
Lei YU01539e72019-07-31 10:57:38 +080065 {
66 version = variant_ns::get<std::string>(propertyValue);
67 }
68 }
69 }
70 else if (interfaceName == FILEPATH_IFACE)
71 {
72 const auto& it = propertyMap.find("Path");
73 if (it != propertyMap.end())
74 {
75 filePath = variant_ns::get<std::string>(it->second);
76 }
77 }
78 }
79 if ((filePath.empty()) || (purpose == VersionPurpose::Unknown))
80 {
81 return;
82 }
83
84 // Version id is the last item in the path
85 auto pos = path.rfind("/");
86 if (pos == std::string::npos)
87 {
88 log<level::ERR>("No version id found in object path",
89 entry("OBJPATH=%s", path.c_str()));
90 return;
91 }
92
93 auto versionId = path.substr(pos + 1);
94
95 if (activations.find(versionId) == activations.end())
96 {
97 // Determine the Activation state by processing the given image dir.
Lei YU91029442019-08-01 15:57:31 +080098 AssociationList associations;
Lei YU58c26e32019-09-27 17:52:06 +080099 auto activationState = Activation::Status::Ready;
Lei YU01539e72019-07-31 10:57:38 +0800100
Lei YU91029442019-08-01 15:57:31 +0800101 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
102 ACTIVATION_REV_ASSOCIATION,
Lei YU5e0dcb32019-08-02 18:04:34 +0800103 PSU_INVENTORY_PATH_BASE));
Lei YU91029442019-08-01 15:57:31 +0800104
Lei YU01539e72019-07-31 10:57:38 +0800105 fs::path manifestPath(filePath);
106 manifestPath /= MANIFEST_FILE;
Lei YU58c26e32019-09-27 17:52:06 +0800107 std::string extendedVersion =
108 Version::getValue(manifestPath, {MANIFEST_EXTENDED_VERSION});
Lei YU01539e72019-07-31 10:57:38 +0800109
Lei YU99301372019-09-29 16:27:12 +0800110 auto activation =
111 createActivationObject(path, versionId, extendedVersion,
112 activationState, associations, filePath);
Lei YU01539e72019-07-31 10:57:38 +0800113 activations.emplace(versionId, std::move(activation));
114
115 auto versionPtr =
Lei YU99301372019-09-29 16:27:12 +0800116 createVersionObject(path, versionId, version, purpose);
Lei YU01539e72019-07-31 10:57:38 +0800117 versions.emplace(versionId, std::move(versionPtr));
118 }
119 return;
120}
121
Lei YUa5c47bb2019-09-29 11:28:53 +0800122void ItemUpdater::erase(const std::string& versionId)
Lei YU01539e72019-07-31 10:57:38 +0800123{
124 auto it = versions.find(versionId);
125 if (it == versions.end())
126 {
127 log<level::ERR>(("Error: Failed to find version " + versionId +
128 " in item updater versions map."
129 " Unable to remove.")
130 .c_str());
131 }
132 else
133 {
134 versions.erase(versionId);
135 }
136
137 // Removing entry in activations map
138 auto ita = activations.find(versionId);
139 if (ita == activations.end())
140 {
141 log<level::ERR>(("Error: Failed to find version " + versionId +
142 " in item updater activations map."
143 " Unable to remove.")
144 .c_str());
145 }
146 else
147 {
148 activations.erase(versionId);
149 }
150}
151
Lei YU91029442019-08-01 15:57:31 +0800152void ItemUpdater::createActiveAssociation(const std::string& path)
153{
154 assocs.emplace_back(
155 std::make_tuple(ACTIVE_FWD_ASSOCIATION, ACTIVE_REV_ASSOCIATION, path));
156 associations(assocs);
157}
158
Lei YUad90ad52019-08-06 11:19:28 +0800159void ItemUpdater::addFunctionalAssociation(const std::string& path)
Lei YU91029442019-08-01 15:57:31 +0800160{
Lei YU91029442019-08-01 15:57:31 +0800161 assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION,
162 FUNCTIONAL_REV_ASSOCIATION, path));
163 associations(assocs);
164}
165
166void ItemUpdater::removeAssociation(const std::string& path)
167{
168 for (auto iter = assocs.begin(); iter != assocs.end();)
169 {
170 if ((std::get<2>(*iter)).compare(path) == 0)
171 {
172 iter = assocs.erase(iter);
173 associations(assocs);
174 }
175 else
176 {
177 ++iter;
178 }
179 }
180}
181
Lei YU01539e72019-07-31 10:57:38 +0800182std::unique_ptr<Activation> ItemUpdater::createActivationObject(
183 const std::string& path, const std::string& versionId,
Lei YU58c26e32019-09-27 17:52:06 +0800184 const std::string& extVersion, Activation::Status activationStatus,
Lei YU99301372019-09-29 16:27:12 +0800185 const AssociationList& assocs, const std::string& filePath)
Lei YU01539e72019-07-31 10:57:38 +0800186{
187 return std::make_unique<Activation>(bus, path, versionId, extVersion,
Lei YU99301372019-09-29 16:27:12 +0800188 activationStatus, assocs, this,
189 filePath);
Lei YU01539e72019-07-31 10:57:38 +0800190}
191
Lei YUad90ad52019-08-06 11:19:28 +0800192void ItemUpdater::createPsuObject(const std::string& psuInventoryPath,
193 const std::string& psuVersion)
194{
195 auto versionId = utils::getVersionId(psuVersion);
196 auto path = std::string(SOFTWARE_OBJPATH) + "/" + versionId;
197
198 auto it = activations.find(versionId);
199 if (it != activations.end())
200 {
201 // The versionId is already created, associate the path
202 auto associations = it->second->associations();
203 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
204 ACTIVATION_REV_ASSOCIATION,
205 psuInventoryPath));
206 it->second->associations(associations);
Lei YUbd3b0072019-08-08 13:09:50 +0800207 psuPathActivationMap.emplace(psuInventoryPath, it->second);
Lei YUad90ad52019-08-06 11:19:28 +0800208 }
209 else
210 {
211 // Create a new object for running PSU inventory
212 AssociationList associations;
Lei YU58c26e32019-09-27 17:52:06 +0800213 auto activationState = Activation::Status::Active;
Lei YUad90ad52019-08-06 11:19:28 +0800214
215 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
216 ACTIVATION_REV_ASSOCIATION,
217 psuInventoryPath));
218
Lei YU99301372019-09-29 16:27:12 +0800219 auto activation = createActivationObject(
220 path, versionId, "", activationState, associations, "");
Lei YUad90ad52019-08-06 11:19:28 +0800221 activations.emplace(versionId, std::move(activation));
Lei YUbd3b0072019-08-08 13:09:50 +0800222 psuPathActivationMap.emplace(psuInventoryPath, activations[versionId]);
Lei YUad90ad52019-08-06 11:19:28 +0800223
224 auto versionPtr = createVersionObject(path, versionId, psuVersion,
Lei YU99301372019-09-29 16:27:12 +0800225 VersionPurpose::PSU);
Lei YUad90ad52019-08-06 11:19:28 +0800226 versions.emplace(versionId, std::move(versionPtr));
227
228 createActiveAssociation(path);
229 addFunctionalAssociation(path);
230 }
231}
232
Lei YUbd3b0072019-08-08 13:09:50 +0800233void ItemUpdater::removePsuObject(const std::string& psuInventoryPath)
234{
Lei YUbd3b0072019-08-08 13:09:50 +0800235 auto it = psuPathActivationMap.find(psuInventoryPath);
236 if (it == psuPathActivationMap.end())
237 {
238 log<level::ERR>("No Activation found for PSU",
239 entry("PSUPATH=%s", psuInventoryPath.c_str()));
240 return;
241 }
242 const auto& activationPtr = it->second;
243 psuPathActivationMap.erase(psuInventoryPath);
244
245 auto associations = activationPtr->associations();
246 for (auto iter = associations.begin(); iter != associations.end();)
247 {
248 if ((std::get<2>(*iter)).compare(psuInventoryPath) == 0)
249 {
250 iter = associations.erase(iter);
251 }
252 else
253 {
254 ++iter;
255 }
256 }
257 if (associations.empty())
258 {
259 // Remove the activation
Lei YUa5c47bb2019-09-29 11:28:53 +0800260 erase(activationPtr->getVersionId());
Lei YUbd3b0072019-08-08 13:09:50 +0800261 }
262 else
263 {
264 // Update association
265 activationPtr->associations(associations);
266 }
267}
268
Lei YU01539e72019-07-31 10:57:38 +0800269std::unique_ptr<Version> ItemUpdater::createVersionObject(
270 const std::string& objPath, const std::string& versionId,
271 const std::string& versionString,
272 sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose
Lei YU99301372019-09-29 16:27:12 +0800273 versionPurpose)
Lei YU01539e72019-07-31 10:57:38 +0800274{
Lei YU65207482019-10-11 16:39:36 +0800275 versionStrings.insert(versionString);
Lei YU01539e72019-07-31 10:57:38 +0800276 auto version = std::make_unique<Version>(
Lei YU99301372019-09-29 16:27:12 +0800277 bus, objPath, versionId, versionString, versionPurpose,
Lei YU01539e72019-07-31 10:57:38 +0800278 std::bind(&ItemUpdater::erase, this, std::placeholders::_1));
279 return version;
280}
281
Lei YUa2c2cd72019-08-09 15:54:10 +0800282void ItemUpdater::onPsuInventoryChangedMsg(sdbusplus::message::message& msg)
Lei YUad90ad52019-08-06 11:19:28 +0800283{
Lei YUbd3b0072019-08-08 13:09:50 +0800284 using Interface = std::string;
Lei YUbd3b0072019-08-08 13:09:50 +0800285 Interface interface;
286 Properties properties;
Lei YUbd3b0072019-08-08 13:09:50 +0800287 std::string psuPath = msg.get_path();
288
289 msg.read(interface, properties);
Lei YUa2c2cd72019-08-09 15:54:10 +0800290 onPsuInventoryChanged(psuPath, properties);
291}
292
293void ItemUpdater::onPsuInventoryChanged(const std::string& psuPath,
294 const Properties& properties)
295{
Lei YUdcaf8932019-09-09 16:09:35 +0800296 bool present;
297 std::string version;
Lei YUbd3b0072019-08-08 13:09:50 +0800298
Lei YUdcaf8932019-09-09 16:09:35 +0800299 // Only present property is interested
Lei YUf77189f2019-08-07 14:26:30 +0800300 auto p = properties.find(PRESENT);
Lei YUdcaf8932019-09-09 16:09:35 +0800301 if (p == properties.end())
Lei YUbd3b0072019-08-08 13:09:50 +0800302 {
303 return;
304 }
Lei YUdcaf8932019-09-09 16:09:35 +0800305 present = sdbusplus::message::variant_ns::get<bool>(p->second);
Lei YUbd3b0072019-08-08 13:09:50 +0800306
Lei YUdcaf8932019-09-09 16:09:35 +0800307 if (present)
Lei YUbd3b0072019-08-08 13:09:50 +0800308 {
Lei YUdcaf8932019-09-09 16:09:35 +0800309 version = utils::getVersion(psuPath);
310 if (!version.empty())
Lei YUbd3b0072019-08-08 13:09:50 +0800311 {
Lei YUdcaf8932019-09-09 16:09:35 +0800312 createPsuObject(psuPath, version);
Lei YUbd3b0072019-08-08 13:09:50 +0800313 }
Lei YUbd3b0072019-08-08 13:09:50 +0800314 }
315 else
316 {
Lei YUbd3b0072019-08-08 13:09:50 +0800317 // Remove object or association
318 removePsuObject(psuPath);
319 }
Lei YUad90ad52019-08-06 11:19:28 +0800320}
321
322void ItemUpdater::processPSUImage()
323{
324 auto paths = utils::getPSUInventoryPath(bus);
325 for (const auto& p : paths)
326 {
Lei YU5f3584d2019-08-27 16:28:53 +0800327 auto service = utils::getService(bus, p.c_str(), ITEM_IFACE);
Lei YUad90ad52019-08-06 11:19:28 +0800328 auto present = utils::getProperty<bool>(bus, service.c_str(), p.c_str(),
Lei YUf77189f2019-08-07 14:26:30 +0800329 ITEM_IFACE, PRESENT);
Lei YU5f3584d2019-08-27 16:28:53 +0800330 auto version = utils::getVersion(p);
Lei YUad90ad52019-08-06 11:19:28 +0800331 if (present && !version.empty())
332 {
333 createPsuObject(p, version);
334 }
Lei YUbd3b0072019-08-08 13:09:50 +0800335 // Add matches for PSU Inventory's property changes
336 psuMatches.emplace_back(
Lei YUdcaf8932019-09-09 16:09:35 +0800337 bus, MatchRules::propertiesChanged(p, ITEM_IFACE),
Lei YUa2c2cd72019-08-09 15:54:10 +0800338 std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this,
Lei YUbd3b0072019-08-08 13:09:50 +0800339 std::placeholders::_1));
Lei YUad90ad52019-08-06 11:19:28 +0800340 }
341}
342
Lei YU58c26e32019-09-27 17:52:06 +0800343void ItemUpdater::processStoredImage()
344{
345 scanDirectory(IMG_DIR_BUILTIN);
346 scanDirectory(IMG_DIR_PERSIST);
347}
348
349void ItemUpdater::scanDirectory(const fs::path& dir)
350{
351 // The directory shall put PSU images in directories named with model
352 if (!fs::exists(dir))
353 {
354 // Skip
355 return;
356 }
357 if (!fs::is_directory(dir))
358 {
359 log<level::ERR>("The path is not a directory",
360 entry("PATH=%s", dir.c_str()));
361 return;
362 }
363 for (const auto& d : fs::directory_iterator(dir))
364 {
365 // If the model in manifest does not match the dir name
366 // Log a warning and skip it
367 auto path = d.path();
368 auto manifest = path / MANIFEST_FILE;
369 if (fs::exists(manifest))
370 {
371 auto ret = Version::getValues(
372 manifest.string(),
373 {MANIFEST_VERSION, MANIFEST_EXTENDED_VERSION});
374 auto version = ret[MANIFEST_VERSION];
375 auto extVersion = ret[MANIFEST_EXTENDED_VERSION];
376 auto info = Version::getExtVersionInfo(extVersion);
377 auto model = info["model"];
378 if (path.stem() != model)
379 {
380 log<level::ERR>("Unmatched model",
381 entry("PATH=%s", path.c_str()),
382 entry("MODEL=%s", model.c_str()));
383 continue;
384 }
385 auto versionId = utils::getVersionId(version);
386 auto it = activations.find(versionId);
387 if (it == activations.end())
388 {
389 // This is a version that is different than the running PSUs
390 auto activationState = Activation::Status::Ready;
391 auto purpose = VersionPurpose::PSU;
392 auto objPath = std::string(SOFTWARE_OBJPATH) + "/" + versionId;
393
394 auto activation = createActivationObject(
395 objPath, versionId, extVersion, activationState, {}, path);
396 activations.emplace(versionId, std::move(activation));
397
398 auto versionPtr =
399 createVersionObject(objPath, versionId, version, purpose);
400 versions.emplace(versionId, std::move(versionPtr));
401 }
402 else
403 {
404 // This is a version that a running PSU is using, set the path
405 // on the version object
406 it->second->path(path);
407 }
408 }
409 else
410 {
411 log<level::ERR>("No MANIFEST found",
412 entry("PATH=%s", path.c_str()));
413 }
414 }
415}
416
Lei YU65207482019-10-11 16:39:36 +0800417std::optional<std::string> ItemUpdater::getLatestVersionId()
418{
419 auto latestVersion = utils::getLatestVersion(versionStrings);
420 if (latestVersion.empty())
421 {
422 return {};
423 }
424
425 std::optional<std::string> versionId;
426 for (const auto& v : versions)
427 {
428 if (v.second->version() == latestVersion)
429 {
430 versionId = v.first;
431 break;
432 }
433 }
434 assert(versionId.has_value());
435 return versionId;
436}
437
Lei YU63f9e712019-10-12 15:16:55 +0800438void ItemUpdater::syncToLatestImage()
439{
440 auto latestVersionId = getLatestVersionId();
441 if (!latestVersionId)
442 {
443 return;
444 }
445 const auto& it = activations.find(*latestVersionId);
446 assert(it != activations.end());
447 const auto& activation = it->second;
448 const auto& assocs = activation->associations();
449
450 auto paths = utils::getPSUInventoryPath(bus);
451 for (const auto& p : paths)
452 {
453 // As long as there is a PSU is not associated with the latest image,
454 // run the activation so that all PSUs are running the same latest
455 // image.
456 if (!utils::isAssociated(p, assocs))
457 {
458 log<level::INFO>("Automatically update PSU",
459 entry("VERSION_ID=%s", latestVersionId->c_str()));
460 invokeActivation(activation);
461 break;
462 }
463 }
464}
465
466void ItemUpdater::invokeActivation(
467 const std::unique_ptr<Activation>& activation)
468{
469 activation->requestedActivation(Activation::RequestedActivations::Active);
470}
471
Lei YU01539e72019-07-31 10:57:38 +0800472} // namespace updater
473} // namespace software
474} // namespace phosphor