Sync PSU images on service startup
On service startup, it shall check the PSU images, find a latest
version, and update to the PSUs that are not with this version.
Tested: With dummy image and service, test on Witherspoon with 2
different running PSU software:
* When startup without stored image, the serive tries to update an older
PSU but it does not have PSU image, so it's skipped with below journal
log:
Automatically update PSU
No image for the activation, skipped
* When startup with an older image stored in BMC, it behaves the same as
above.
* When startup with a new image stored in BMC, it updates the PSUs, with
below example journal log:
Automatically update PSU
Starting Update PSU
/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0
/var/lib/obmc/psu/2B1D...
Started Update PSU
/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0
/var/lib/obmc/psu/2B1D.
Starting Update PSU
/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply1
/var/lib/obmc/psu/2B1D...
Started Update PSU
/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply1
/var/lib/obmc/psu/2B1D.
Signed-off-by: Lei YU <mine260309@gmail.com>
Change-Id: I6d676c5a4441685fb2b5920455f439c00f6097af
diff --git a/src/activation.cpp b/src/activation.cpp
index efff61b..cab9acd 100644
--- a/src/activation.cpp
+++ b/src/activation.cpp
@@ -49,7 +49,11 @@
(SoftwareActivation::requestedActivation() !=
SoftwareActivation::RequestedActivations::Active))
{
- if ((activation() == Status::Ready) || (activation() == Status::Failed))
+ // PSU image could be activated even when it's in active,
+ // e.g. in case a PSU is replaced and has a older image, it will be
+ // updated with the running PSU image that is stored in BMC.
+ if ((activation() == Status::Ready) ||
+ (activation() == Status::Failed) || activation() == Status::Active)
{
activation(Status::Activating);
}
@@ -141,6 +145,13 @@
Activation::Status Activation::startActivation()
{
+ // Check if the activation has file path
+ if (path().empty())
+ {
+ log<level::WARNING>("No image for the activation, skipped",
+ entry("VERSION_ID=%s", versionId.c_str()));
+ return activation(); // Return the previous activation status
+ }
if (!activationProgress)
{
activationProgress = std::make_unique<ActivationProgress>(bus, objPath);
@@ -162,6 +173,12 @@
{
if (isCompatible(p))
{
+ if (utils::isAssociated(p, associations()))
+ {
+ log<level::NOTICE>("PSU already running the image, skipping",
+ entry("PSU=%s", p.c_str()));
+ continue;
+ }
psuQueue.push(p);
}
else
@@ -173,8 +190,8 @@
if (psuQueue.empty())
{
- log<level::ERR>("No PSU compatible with the software");
- return Status::Failed;
+ log<level::WARNING>("No PSU compatible with the software");
+ return activation(); // Return the previous activation status
}
// The progress to be increased for each successful update of PSU
diff --git a/src/activation.hpp b/src/activation.hpp
index be373c5..669686e 100644
--- a/src/activation.hpp
+++ b/src/activation.hpp
@@ -176,6 +176,12 @@
RequestedActivations
requestedActivation(RequestedActivations value) override;
+ /** @brief Get the object path */
+ const std::string& getObjectPath() const
+ {
+ return objPath;
+ }
+
/** @brief Get the version ID */
const std::string& getVersionId() const
{
diff --git a/src/item_updater.cpp b/src/item_updater.cpp
index 13e6bf4..acbc943 100644
--- a/src/item_updater.cpp
+++ b/src/item_updater.cpp
@@ -435,6 +435,40 @@
return versionId;
}
+void ItemUpdater::syncToLatestImage()
+{
+ auto latestVersionId = getLatestVersionId();
+ if (!latestVersionId)
+ {
+ return;
+ }
+ const auto& it = activations.find(*latestVersionId);
+ assert(it != activations.end());
+ const auto& activation = it->second;
+ const auto& assocs = activation->associations();
+
+ auto paths = utils::getPSUInventoryPath(bus);
+ for (const auto& p : paths)
+ {
+ // As long as there is a PSU is not associated with the latest image,
+ // run the activation so that all PSUs are running the same latest
+ // image.
+ if (!utils::isAssociated(p, assocs))
+ {
+ log<level::INFO>("Automatically update PSU",
+ entry("VERSION_ID=%s", latestVersionId->c_str()));
+ invokeActivation(activation);
+ break;
+ }
+ }
+}
+
+void ItemUpdater::invokeActivation(
+ const std::unique_ptr<Activation>& activation)
+{
+ activation->requestedActivation(Activation::RequestedActivations::Active);
+}
+
} // namespace updater
} // namespace software
} // namespace phosphor
diff --git a/src/item_updater.hpp b/src/item_updater.hpp
index 2f23b1e..b713454 100644
--- a/src/item_updater.hpp
+++ b/src/item_updater.hpp
@@ -55,7 +55,7 @@
{
processPSUImage();
processStoredImage();
- getLatestVersionId();
+ syncToLatestImage();
}
/** @brief Deletes version
@@ -154,6 +154,12 @@
/** @brief Get the versionId of the latest PSU version */
std::optional<std::string> getLatestVersionId();
+ /** @brief Update PSUs to the latest version */
+ void syncToLatestImage();
+
+ /** @brief Invoke the activation via DBus */
+ void invokeActivation(const std::unique_ptr<Activation>& activation);
+
/** @brief Persistent sdbusplus D-Bus bus connection. */
sdbusplus::bus::bus& bus;
diff --git a/test/test_activation.cpp b/test/test_activation.cpp
index a402758..97efe79 100644
--- a/test/test_activation.cpp
+++ b/test/test_activation.cpp
@@ -30,6 +30,7 @@
.WillByDefault(Return(any(PropertyType(std::string("TestManu")))));
ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(MODEL)))
.WillByDefault(Return(any(PropertyType(std::string("TestModel")))));
+ ON_CALL(mockedUtils, isAssociated(_, _)).WillByDefault(Return(false));
}
~TestActivation()
{
@@ -64,7 +65,7 @@
std::unique_ptr<Activation> activation;
std::string versionId = "abcdefgh";
std::string extVersion = "manufacturer=TestManu,model=TestModel";
- std::string filePath = "";
+ std::string filePath = "/tmp/images/abcdefgh";
std::string dBusPath = std::string(SOFTWARE_OBJPATH) + "/" + versionId;
Status status = Status::Ready;
AssociationList associations;
@@ -233,7 +234,7 @@
.WillByDefault(Return(std::vector<std::string>({psu0})));
activation->requestedActivation(RequestedStatus::Active);
- EXPECT_EQ(Status::Failed, activation->activation());
+ EXPECT_EQ(Status::Ready, activation->activation());
}
TEST_F(TestActivation, doUpdateOnePSUManufactureNotCompatible)
@@ -247,7 +248,7 @@
.WillByDefault(Return(std::vector<std::string>({psu0})));
activation->requestedActivation(RequestedStatus::Active);
- EXPECT_EQ(Status::Failed, activation->activation());
+ EXPECT_EQ(Status::Ready, activation->activation());
}
TEST_F(TestActivation, doUpdateOnePSUSelfManufactureIsEmpty)
@@ -315,3 +316,73 @@
onUpdateDone();
EXPECT_EQ(Status::Active, activation->activation());
}
+
+TEST_F(TestActivation, doUpdateWhenNoFilePathInActiveState)
+{
+ filePath = "";
+ status = Status::Active; // Typically, a running PSU software is active
+ // without file path
+ constexpr auto psu0 = "/com/example/inventory/psu0";
+ activation = std::make_unique<Activation>(
+ mockedBus, dBusPath, versionId, extVersion, status, associations,
+ &mockedAssociationInterface, filePath);
+
+ ON_CALL(mockedUtils, getPSUInventoryPath(_))
+ .WillByDefault(
+ Return(std::vector<std::string>({psu0}))); // One PSU inventory
+
+ // There shall be no DBus call to start update service
+ EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _,
+ StrEq("StartUnit")))
+ .Times(0);
+
+ activation->requestedActivation(RequestedStatus::Active);
+ EXPECT_EQ(Status::Active, activation->activation());
+}
+
+TEST_F(TestActivation, doUpdateWhenNoFilePathInReadyState)
+{
+ filePath = "";
+ status = Status::Ready; // Usually a Ready activation should have file path,
+ // but we are testing this case as well
+ constexpr auto psu0 = "/com/example/inventory/psu0";
+ activation = std::make_unique<Activation>(
+ mockedBus, dBusPath, versionId, extVersion, status, associations,
+ &mockedAssociationInterface, filePath);
+
+ ON_CALL(mockedUtils, getPSUInventoryPath(_))
+ .WillByDefault(
+ Return(std::vector<std::string>({psu0}))); // One PSU inventory
+
+ // There shall be no DBus call to start update service
+ EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _,
+ StrEq("StartUnit")))
+ .Times(0);
+
+ activation->requestedActivation(RequestedStatus::Active);
+ EXPECT_EQ(Status::Ready, activation->activation());
+}
+
+TEST_F(TestActivation, doUpdateWhenPSUIsAssociated)
+{
+ constexpr auto psu0 = "/com/example/inventory/psu0";
+ status = Status::Active; // Typically, a running PSU software is associated
+ activation = std::make_unique<Activation>(
+ mockedBus, dBusPath, versionId, extVersion, status, associations,
+ &mockedAssociationInterface, filePath);
+
+ ON_CALL(mockedUtils, getPSUInventoryPath(_))
+ .WillByDefault(
+ Return(std::vector<std::string>({psu0}))); // One PSU inventory
+
+ // When PSU is already associated, there shall be no DBus call to start
+ // update service
+ EXPECT_CALL(mockedUtils, isAssociated(StrEq(psu0), _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _,
+ StrEq("StartUnit")))
+ .Times(0);
+
+ activation->requestedActivation(RequestedStatus::Active);
+ EXPECT_EQ(Status::Active, activation->activation());
+}