summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPatrick Venture <venture@google.com>2019-08-16 10:50:18 -0700
committerPatrick Venture <venture@google.com>2019-08-20 08:55:01 -0700
commit02e32376817529e960bb918417bb6a55d646a3a3 (patch)
treed3624e105490d5f51dd1df6d762a07d8e1b1a918
parent51d0c40ad6c9181a172fb1fc14aa8d39b66e7ba7 (diff)
downloadphosphor-host-ipmid-02e32376817529e960bb918417bb6a55d646a3a3.tar.gz
phosphor-host-ipmid-02e32376817529e960bb918417bb6a55d646a3a3.zip
entitymap: add json parsing support
Add a json parsing method that given an entity map in the format: [ { "id" : 1, "containerEntityId" : 2, "containerEntityInstance" : 3, "isList" : false, "isLinked" : false, "entities" : [ {"id" : 1, "instance" : 2}, {"id" : 1, "instance" : 3}, {"id" : 1, "instance" : 4}, {"id" : 1, "instance" : 5} ] } ] is constructed into the entity map used by sensorhandler. This is meant as part of the transition from the entity map in YAML to JSON. This is step 2. Step 1 moved access to the object behind a method. This adds JSON validation and parsing. Step 3 will add a file path to check and parse. Step 4 will provide a call to parse that file if present and use its data if non-empty. Tested: The method added has not been tested beyond unit-test validation. Signed-off-by: Patrick Venture <venture@google.com> Change-Id: Ic29f022d3812fa9e3af775d542ad055629fd5a01
-rw-r--r--Makefile.am1
-rw-r--r--entity_map_json.cpp74
-rw-r--r--entity_map_json.hpp24
-rw-r--r--test/Makefile.am5
-rw-r--r--test/entitymap_json_unittest.cpp223
5 files changed, 327 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am
index dc3e91e..bcac808 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -90,6 +90,7 @@ libipmi20_la_SOURCES = \
apphandler.cpp \
sys_info_param.cpp \
sensorhandler.cpp \
+ entity_map_json.cpp \
storagehandler.cpp \
chassishandler.cpp \
dcmihandler.cpp \
diff --git a/entity_map_json.cpp b/entity_map_json.cpp
new file mode 100644
index 0000000..db45b05
--- /dev/null
+++ b/entity_map_json.cpp
@@ -0,0 +1,74 @@
+#include "entity_map_json.hpp"
+
+#include <exception>
+#include <ipmid/types.hpp>
+#include <nlohmann/json.hpp>
+#include <string>
+#include <utility>
+
+namespace ipmi
+{
+namespace sensor
+{
+
+EntityInfoMap buildJsonEntityMap(const nlohmann::json& data)
+{
+ EntityInfoMap builtMap;
+
+ if (data.type() != nlohmann::json::value_t::array)
+ {
+ return builtMap;
+ }
+
+ try
+ {
+ for (const auto& entry : data)
+ {
+ /* It's an array entry with the following fields: id,
+ * containerEntityId, containerEntityInstance, isList, isLinked,
+ * entities[4]
+ */
+ EntityInfo obj;
+ Id recordId = entry.at("id").get<Id>();
+ obj.containerEntityId =
+ entry.at("containerEntityId").get<uint8_t>();
+ obj.containerEntityInstance =
+ entry.at("containerEntityInstance").get<uint8_t>();
+ obj.isList = entry.at("isList").get<bool>();
+ obj.isLinked = entry.at("isLinked").get<bool>();
+
+ auto jsonEntities = entry.at("entities");
+
+ if (jsonEntities.type() != nlohmann::json::value_t::array)
+ {
+ throw std::runtime_error(
+ "Invalid type for entities entry, must be array");
+ }
+ if (jsonEntities.size() != obj.containedEntities.size())
+ {
+ throw std::runtime_error(
+ "Entities must be in pairs of " +
+ std::to_string(obj.containedEntities.size()));
+ }
+
+ for (std::size_t i = 0; i < obj.containedEntities.size(); i++)
+ {
+ obj.containedEntities[i] = std::make_pair(
+ jsonEntities[i].at("id").get<uint8_t>(),
+ jsonEntities[i].at("instance").get<uint8_t>());
+ }
+
+ builtMap.insert({recordId, obj});
+ }
+ }
+ catch (const std::exception& e)
+ {
+ /* If any entry is invalid, the entire file cannot be trusted. */
+ builtMap.clear();
+ }
+
+ return builtMap;
+}
+
+} // namespace sensor
+} // namespace ipmi
diff --git a/entity_map_json.hpp b/entity_map_json.hpp
new file mode 100644
index 0000000..0e9be01
--- /dev/null
+++ b/entity_map_json.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <ipmid/types.hpp>
+#include <nlohmann/json.hpp>
+
+namespace ipmi
+{
+namespace sensor
+{
+
+/**
+ * @brief Given json data validate the data matches the expected format for the
+ * entity map configuration and parse the data into a map of the entities.
+ *
+ * If any entry is invalid, the entire contents passed in is disregarded as
+ * possibly corrupt.
+ *
+ * @param[in] data - the json data
+ * @return the map
+ */
+EntityInfoMap buildJsonEntityMap(const nlohmann::json& data);
+
+} // namespace sensor
+} // namespace ipmi
diff --git a/test/Makefile.am b/test/Makefile.am
index e510230..e4ff261 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -26,6 +26,11 @@ AM_LDFLAGS = $(GTEST_MAIN_LIBS) $(OESDK_TESTCASE_FLAGS)
check_PROGRAMS =
TESTS = $(check_PROGRAMS)
+entitymap_json_unittest_SOURCES = entitymap_json_unittest.cpp
+entitymap_json_unittest_LDADD = $(top_builddir)/entity_map_json.o -lgmock
+
+check_PROGRAMS += entitymap_json_unittest
+
# Build/add sample_unittest to test suite
sample_unittest_CPPFLAGS = -Igtest $(GTEST_CPPFLAGS) $(AM_CPPFLAGS)
sample_unittest_CXXFLAGS = $(PTHREAD_CFLAGS) $(CODE_COVERAGE_CXXFLAGS) \
diff --git a/test/entitymap_json_unittest.cpp b/test/entitymap_json_unittest.cpp
new file mode 100644
index 0000000..a380a9e
--- /dev/null
+++ b/test/entitymap_json_unittest.cpp
@@ -0,0 +1,223 @@
+#include "entity_map_json.hpp"
+
+#include <ipmid/types.hpp>
+#include <nlohmann/json.hpp>
+#include <utility>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace ipmi
+{
+namespace sensor
+{
+
+namespace
+{
+using ::testing::IsEmpty;
+
+TEST(ValidateJson, FailWithNonArrayReturnsEmpty)
+{
+ /* The entity map input json is expected to be an array of objects. */
+ auto j = R"(
+ {
+ "id" : 1,
+ "containerEntityId" : 2,
+ "containerEntityInstance" : 3,
+ "isList" : false,
+ "isLinked" : false,
+ "entities" : [
+ {"id" : 1, "instance" : 2},
+ {"id" : 1, "instance" : 3},
+ {"id" : 1, "instance" : 4},
+ {"id" : 1, "instance" : 5}
+ ]
+ }
+ )"_json;
+
+ EXPECT_THAT(buildJsonEntityMap(j), IsEmpty());
+}
+
+TEST(ValidateJson, FailWithMissingFieldReturnsEmpty)
+{
+ /* There are many required fields, let's just validate that if one is
+ * missing, it returns the empty map.
+ */
+ auto j = R"(
+ [
+ {
+ "id" : 1,
+ "containerEntityId" : 2,
+ "containerEntityInstance" : 3,
+ "isList" : false,
+ "entities" : [
+ {"id" : 1, "instance" : 2},
+ {"id" : 1, "instance" : 3},
+ {"id" : 1, "instance" : 4},
+ {"id" : 1, "instance" : 5}
+ ]
+ }
+ ]
+ )"_json;
+
+ EXPECT_THAT(buildJsonEntityMap(j), IsEmpty());
+}
+
+TEST(ValidateJson, AllValidEntryReturnsExpectedMap)
+{
+ /* Boring test where we provide completely valid information and expect the
+ * resulting map contains that information.
+ */
+ auto j = R"(
+ [
+ {
+ "id" : 1,
+ "containerEntityId" : 2,
+ "containerEntityInstance" : 3,
+ "isList" : false,
+ "isLinked" : false,
+ "entities" : [
+ {"id" : 1, "instance" : 2},
+ {"id" : 1, "instance" : 3},
+ {"id" : 1, "instance" : 4},
+ {"id" : 1, "instance" : 5}
+ ]
+ }
+ ]
+ )"_json;
+
+ auto map = buildJsonEntityMap(j);
+ EXPECT_FALSE(map.find(1) == map.end());
+ auto entry = map.find(1);
+ EXPECT_EQ(entry->first, 1);
+
+ /* TODO: someone could write an equality operator for this object. */
+ EXPECT_EQ(entry->second.containerEntityId, 2);
+ EXPECT_EQ(entry->second.containerEntityInstance, 3);
+ EXPECT_FALSE(entry->second.isList);
+ EXPECT_FALSE(entry->second.isLinked);
+ ContainedEntitiesArray expected = {
+ std::make_pair(1, 2), std::make_pair(1, 3), std::make_pair(1, 4),
+ std::make_pair(1, 5)};
+ EXPECT_EQ(entry->second.containedEntities, expected);
+}
+
+TEST(ValidateJson, EntryHasInsufficientContainerEntryCountReturnsEmpty)
+{
+ /* The container must have four pairs. (I don't know why, and maybe this
+ * restriction will change).
+ */
+ auto j = R"(
+ [
+ {
+ "id" : 1,
+ "containerEntityId" : 2,
+ "containerEntityInstance" : 3,
+ "isList" : false,
+ "isLinked" : false,
+ "entities" : [
+ {"id" : 1, "instance" : 2},
+ {"id" : 1, "instance" : 3},
+ {"id" : 1, "instance" : 4}
+ ]
+ }
+ ]
+ )"_json;
+
+ EXPECT_THAT(buildJsonEntityMap(j), IsEmpty());
+}
+
+TEST(ValidateJson, ThereAreTwoEntriesOneInvalidReturnsEmpty)
+{
+ /* If any entry in the file is corrupt, the file is disregarded. */
+ auto j = R"(
+ [
+ {
+ "id" : 1,
+ "containerEntityId" : 2,
+ "containerEntityInstance" : 3,
+ "isList" : false,
+ "isLinked" : false,
+ "entities" : [
+ {"id" : 1, "instance" : 2},
+ {"id" : 1, "instance" : 3},
+ {"id" : 1, "instance" : 4},
+ {"id" : 1, "instance" : 5}
+ ]
+ },
+ {
+ "id" : 2,
+ "containerEntityId" : 2,
+ "containerEntityInstance" : 3,
+ "isList" : false,
+ "isLinked" : false,
+ "entities" : [
+ {"id" : 1, "instance" : 2},
+ {"id" : 1, "instance" : 3},
+ {"id" : 1, "instance" : 4}
+ ]
+ }
+ ]
+ )"_json;
+
+ EXPECT_THAT(buildJsonEntityMap(j), IsEmpty());
+}
+
+TEST(ValidateJson, ThereAreTwoEntriesBothValidReturnsBoth)
+{
+ /* The map supports more than one entry, just validate this. */
+ auto j = R"(
+ [
+ {
+ "id" : 1,
+ "containerEntityId" : 2,
+ "containerEntityInstance" : 3,
+ "isList" : false,
+ "isLinked" : false,
+ "entities" : [
+ {"id" : 1, "instance" : 2},
+ {"id" : 1, "instance" : 3},
+ {"id" : 1, "instance" : 4},
+ {"id" : 1, "instance" : 5}
+ ]
+ },
+ {
+ "id" : 2,
+ "containerEntityId" : 2,
+ "containerEntityInstance" : 3,
+ "isList" : false,
+ "isLinked" : false,
+ "entities" : [
+ {"id" : 1, "instance" : 6},
+ {"id" : 1, "instance" : 7},
+ {"id" : 1, "instance" : 8},
+ {"id" : 1, "instance" : 9}
+ ]
+ }
+ ]
+ )"_json;
+
+ auto map = buildJsonEntityMap(j);
+ EXPECT_FALSE(map.find(1) == map.end());
+ EXPECT_FALSE(map.find(2) == map.end());
+
+ auto entry = map.find(1);
+ EXPECT_EQ(entry->first, 1);
+ EXPECT_EQ(entry->second.containerEntityId, 2);
+ EXPECT_EQ(entry->second.containerEntityInstance, 3);
+ EXPECT_FALSE(entry->second.isList);
+ EXPECT_FALSE(entry->second.isLinked);
+ ContainedEntitiesArray expected = {
+ std::make_pair(1, 2), std::make_pair(1, 3), std::make_pair(1, 4),
+ std::make_pair(1, 5)};
+ EXPECT_EQ(entry->second.containedEntities, expected);
+
+ entry = map.find(2);
+ expected = {std::make_pair(1, 6), std::make_pair(1, 7),
+ std::make_pair(1, 8), std::make_pair(1, 9)};
+ EXPECT_EQ(entry->second.containedEntities, expected);
+}
+
+} // namespace
+} // namespace sensor
+} // namespace ipmi
OpenPOWER on IntegriCloud