PEL: Save AdditionalData in a UserData section
Save the contents of the AdditionalData OpenBMC event log property as
JSON in a UserData PEL section.
For example, if the AdditionalData property, which is an array of
strings, looks like:
["KEY1=VALUE1", "KEY=VALUE2"]
Then the data stored as ASCII text in the UserData section is:
{"KEY1":"VALUE1","KEY2":"VALUE2"}
If one of the keys is "ESEL", then that entry is removed from the
UserData output as that contains a full raw PEL from the host sent down
as ASCII text.
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Ia6ffabb39fdb4315ec2152744414e44f7d2ec4aa
diff --git a/extensions/openpower-pels/additional_data.hpp b/extensions/openpower-pels/additional_data.hpp
index b855054..f133e0f 100644
--- a/extensions/openpower-pels/additional_data.hpp
+++ b/extensions/openpower-pels/additional_data.hpp
@@ -1,5 +1,6 @@
#pragma once
#include <map>
+#include <nlohmann/json.hpp>
#include <optional>
#include <string>
#include <vector>
@@ -67,6 +68,39 @@
return std::nullopt;
}
+ /**
+ * @brief Remove a key/value pair from the contained data
+ *
+ * @param[in] key - The key of the entry to remove
+ */
+ void remove(const std::string& key)
+ {
+ _data.erase(key);
+ }
+
+ /**
+ * @brief Says if the object has no data
+ *
+ * @return bool true if the object is empty
+ */
+ inline bool empty() const
+ {
+ return _data.empty();
+ }
+
+ /**
+ * @brief Returns the contained data as a JSON object
+ *
+ * Looks like: {"key1":"value1","key2":"value2"}
+ *
+ * @return json - The JSON object
+ */
+ nlohmann::json toJSON() const
+ {
+ nlohmann::json j = _data;
+ return j;
+ }
+
private:
/**
* @brief a map of keys to values
diff --git a/extensions/openpower-pels/pel.cpp b/extensions/openpower-pels/pel.cpp
index ffd4974..af904ff 100644
--- a/extensions/openpower-pels/pel.cpp
+++ b/extensions/openpower-pels/pel.cpp
@@ -6,6 +6,7 @@
#include "section_factory.hpp"
#include "src.hpp"
#include "stream.hpp"
+#include "user_data_formats.hpp"
#include <phosphor-logging/log.hpp>
@@ -30,6 +31,12 @@
auto mtms = std::make_unique<FailingMTMS>(dataIface);
_optionalSections.push_back(std::move(mtms));
+ if (!additionalData.empty())
+ {
+ auto ud = util::makeADUserDataSection(additionalData);
+ _optionalSections.push_back(std::move(ud));
+ }
+
_ph->sectionCount() = 2 + _optionalSections.size();
}
@@ -134,5 +141,35 @@
return std::nullopt;
}
+namespace util
+{
+
+std::unique_ptr<UserData> makeADUserDataSection(const AdditionalData& ad)
+{
+ assert(!ad.empty());
+ nlohmann::json json;
+
+ // Remove the 'ESEL' entry, as it contains a full PEL in the value.
+ if (ad.getValue("ESEL"))
+ {
+ auto newAD = ad;
+ newAD.remove("ESEL");
+ json = newAD.toJSON();
+ }
+ else
+ {
+ json = ad.toJSON();
+ }
+
+ auto jsonString = json.dump();
+ std::vector<uint8_t> jsonData(jsonString.begin(), jsonString.end());
+
+ return std::make_unique<UserData>(
+ static_cast<uint16_t>(ComponentID::phosphorLogging),
+ static_cast<uint8_t>(UserDataFormat::json),
+ static_cast<uint8_t>(UserDataFormatVersion::json), jsonData);
+}
+
+} // namespace util
} // namespace pels
} // namespace openpower
diff --git a/extensions/openpower-pels/pel.hpp b/extensions/openpower-pels/pel.hpp
index bc857e2..f63cfdd 100644
--- a/extensions/openpower-pels/pel.hpp
+++ b/extensions/openpower-pels/pel.hpp
@@ -5,6 +5,7 @@
#include "private_header.hpp"
#include "registry.hpp"
#include "src.hpp"
+#include "user_data.hpp"
#include "user_header.hpp"
#include <memory>
@@ -249,5 +250,20 @@
std::vector<std::unique_ptr<Section>> _optionalSections;
};
+namespace util
+{
+
+/**
+ * @brief Create a UserData section containing the AdditionalData
+ * contents as a JSON string.
+ *
+ * @param[in] ad - The AdditionalData contents
+ *
+ * @return std::unique_ptr<UserData> - The section
+ */
+std::unique_ptr<UserData> makeADUserDataSection(const AdditionalData& ad);
+
+} // namespace util
+
} // namespace pels
} // namespace openpower
diff --git a/extensions/openpower-pels/user_data_formats.hpp b/extensions/openpower-pels/user_data_formats.hpp
new file mode 100644
index 0000000..53cb5ee
--- /dev/null
+++ b/extensions/openpower-pels/user_data_formats.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+namespace openpower
+{
+namespace pels
+{
+
+enum class UserDataFormat
+{
+ json = 1
+};
+
+enum class UserDataFormatVersion
+{
+ json = 1
+};
+
+} // namespace pels
+} // namespace openpower
diff --git a/test/openpower-pels/additional_data_test.cpp b/test/openpower-pels/additional_data_test.cpp
index 2be06c4..1bf4af7 100644
--- a/test/openpower-pels/additional_data_test.cpp
+++ b/test/openpower-pels/additional_data_test.cpp
@@ -24,4 +24,11 @@
EXPECT_FALSE(ad.getValue("HELLOWORLD"));
EXPECT_FALSE(ad.getValue("VALUE5"));
+
+ auto json = ad.toJSON();
+ std::string expected = R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":""})";
+ EXPECT_EQ(json.dump(), expected);
+
+ ad.remove("KEY1");
+ EXPECT_FALSE(ad.getValue("KEY1"));
}
diff --git a/test/openpower-pels/pel_test.cpp b/test/openpower-pels/pel_test.cpp
index b0c6528..6494c52 100644
--- a/test/openpower-pels/pel_test.cpp
+++ b/test/openpower-pels/pel_test.cpp
@@ -231,3 +231,32 @@
EXPECT_TRUE(foundGeneric);
}
+
+// Create a UserData section out of AdditionalData
+TEST_F(PELTest, MakeUDSectionTest)
+{
+ std::vector<std::string> ad{"KEY1=VALUE1", "KEY2=VALUE2", "KEY3=VALUE3",
+ "ESEL=TEST"};
+ AdditionalData additionalData{ad};
+
+ auto ud = util::makeADUserDataSection(additionalData);
+
+ EXPECT_TRUE(ud->valid());
+ EXPECT_EQ(ud->header().id, 0x5544);
+ EXPECT_EQ(ud->header().version, 0x01);
+ EXPECT_EQ(ud->header().subType, 0x01);
+ EXPECT_EQ(ud->header().componentID, 0x2000);
+
+ const auto& d = ud->data();
+
+ std::string jsonString{d.begin(), d.end()};
+ std::string expected =
+ R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":"VALUE3"})";
+ EXPECT_EQ(jsonString, expected);
+
+ // Ensure we can read this as JSON
+ auto newJSON = nlohmann::json::parse(jsonString);
+ EXPECT_EQ(newJSON["KEY1"], "VALUE1");
+ EXPECT_EQ(newJSON["KEY2"], "VALUE2");
+ EXPECT_EQ(newJSON["KEY3"], "VALUE3");
+}
\ No newline at end of file