add json verification for configurations
Add json verificiation for configurations. A configuration is lightly
validated.
Change-Id: I42361daf6ad21d3480e92c3808f5fc8ab8318e0b
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/Makefile.am b/Makefile.am
index cce4f4c..c227281 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -78,6 +78,7 @@
pid/util.cpp \
pid/pidthread.cpp \
threads/busthread.cpp \
+ build/buildjson.cpp \
experiments/drive.cpp \
$(BUILT_SOURCES)
diff --git a/build/buildjson.cpp b/build/buildjson.cpp
new file mode 100644
index 0000000..a5ae502
--- /dev/null
+++ b/build/buildjson.cpp
@@ -0,0 +1,64 @@
+#include "build/buildjson.hpp"
+
+#include "errors/exception.hpp"
+
+#include <fstream>
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+
+void validateJson(const json& data)
+{
+ if (data.count("sensors") != 1)
+ {
+ throw ConfigurationException(
+ "KeyError: 'sensors' not found (or found repeatedly)");
+ }
+
+ if (data["sensors"].size() == 0)
+ {
+ throw ConfigurationException(
+ "Invalid Configuration: At least one sensor required");
+ }
+
+ if (data.count("zones") != 1)
+ {
+ throw ConfigurationException(
+ "KeyError: 'zones' not found (or found repeatedly)");
+ }
+
+ for (const auto& zone : data["zones"])
+ {
+ if (zone.count("pids") != 1)
+ {
+ throw ConfigurationException(
+ "KeyError: should only have one 'pids' key per zone.");
+ }
+
+ if (zone["pids"].size() == 0)
+ {
+ throw ConfigurationException(
+ "Invalid Configuration: must be at least one pid per zone.");
+ }
+ }
+}
+
+json parseValidateJson(const std::string& path)
+{
+ std::ifstream jsonFile(path);
+ if (!jsonFile.is_open())
+ {
+ throw ConfigurationException("Unable to open json file");
+ }
+
+ auto data = json::parse(jsonFile, nullptr, false);
+ if (data.is_discarded())
+ {
+ throw ConfigurationException("Invalid json - parse failed");
+ }
+
+ /* Check the data. */
+ validateJson(data);
+
+ return data;
+}
diff --git a/build/buildjson.hpp b/build/buildjson.hpp
new file mode 100644
index 0000000..afc90df
--- /dev/null
+++ b/build/buildjson.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <nlohmann/json.hpp>
+#include <string>
+
+using json = nlohmann::json;
+
+/**
+ * Given json data, validate the minimum.
+ * The json data must be valid, and must contain two keys:
+ * sensors, and zones.
+ *
+ * @param[in] data - the json data.
+ * @return nothing - throws exceptions on invalid bits.
+ */
+void validateJson(const json& data);
+
+/**
+ * Given a json configuration file, parse it.
+ *
+ * There must be at least one sensor, and one zone.
+ * That one zone must contain at least one PID.
+ *
+ * @param[in] path - path to the configuration
+ * @return the json data.
+ */
+json parseValidateJson(const std::string& path);
diff --git a/test/Makefile.am b/test/Makefile.am
index 5375eb4..04cf2df 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -18,7 +18,7 @@
pid_thermalcontroller_unittest pid_fancontroller_unittest \
pid_stepwisecontroller_unittest \
dbus_passive_unittest dbus_active_unittest \
- sensors_json_unittest pid_json_unittest
+ sensors_json_unittest pid_json_unittest json_parse_unittest
TESTS = $(check_PROGRAMS)
# Until libconfig is mocked out or replaced, include it.
@@ -65,3 +65,6 @@
pid_json_unittest_SOURCES = pid_json_unittest.cpp
pid_json_unittest_LDADD = $(top_builddir)/pid/buildjson.o
+
+json_parse_unittest_SOURCES = json_parse_unittest.cpp
+json_parse_unittest_LDADD = $(top_builddir)/build/buildjson.o
diff --git a/test/json_parse_unittest.cpp b/test/json_parse_unittest.cpp
new file mode 100644
index 0000000..7d3d449
--- /dev/null
+++ b/test/json_parse_unittest.cpp
@@ -0,0 +1,154 @@
+#include "build/buildjson.hpp"
+#include "errors/exception.hpp"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+TEST(ConfigurationVerificationTest, VerifyHappy)
+{
+ /* Verify a happy configuration throws no exceptions. */
+ auto j2 = R"(
+ {
+ "sensors": [{
+ "name": "fan1",
+ "type": "fan",
+ "readPath": "/xyz/openbmc_project/sensors/fan_tach/fan1"
+ }],
+ "zones": [{
+ "id": 1,
+ "minThermalRpm": 3000.0,
+ "failsafePercent": 75.0,
+ "pids": [{
+ "name": "fan1-5",
+ "type": "fan",
+ "inputs": ["fan1", "fan5"],
+ "setpoint": 90.0,
+ "pid": {
+ "samplePeriod": 0.1,
+ "proportionalCoeff": 0.0,
+ "integralCoeff": 0.0,
+ "feedFwdOffOffsetCoeff": 0.0,
+ "feedFwdGainCoeff": 0.010,
+ "integralLimit_min": 0.0,
+ "integralLimit_max": 0.0,
+ "outLim_min": 30.0,
+ "outLim_max": 100.0,
+ "slewNeg": 0.0,
+ "slewPos": 0.0
+ }
+ }]
+ }]
+ }
+ )"_json;
+
+ validateJson(j2);
+}
+
+TEST(ConfigurationVerificationTest, VerifyNoSensorKey)
+{
+ /* Verify the sensors key must be present. */
+ auto j2 = R"(
+ {
+ "zones": [{
+ "id": 1,
+ "minThermalRpm": 3000.0,
+ "failsafePercent": 75.0,
+ "pids": [{
+ "name": "fan1-5",
+ "type": "fan",
+ "inputs": ["fan1", "fan5"],
+ "setpoint": 90.0,
+ "pid": {
+ "samplePeriod": 0.1,
+ "proportionalCoeff": 0.0,
+ "integralCoeff": 0.0,
+ "feedFwdOffOffsetCoeff": 0.0,
+ "feedFwdGainCoeff": 0.010,
+ "integralLimit_min": 0.0,
+ "integralLimit_max": 0.0,
+ "outLim_min": 30.0,
+ "outLim_max": 100.0,
+ "slewNeg": 0.0,
+ "slewPos": 0.0
+ }
+ }]
+ }]
+ }
+ )"_json;
+
+ EXPECT_THROW(validateJson(j2), ConfigurationException);
+}
+
+TEST(ConfigurationVerificationTest, VerifyNoZoneKey)
+{
+ /* Verify the zones key must be present. */
+ auto j2 = R"(
+ {
+ "sensors": [{
+ "name": "fan1",
+ "type": "fan",
+ "readPath": "/xyz/openbmc_project/sensors/fan_tach/fan1"
+ }]
+ }
+ )"_json;
+
+ EXPECT_THROW(validateJson(j2), ConfigurationException);
+}
+
+TEST(ConfigurationVerificationTest, VerifyNoSensor)
+{
+ /* Verify that there needs to be at least one sensor in the sensors key. */
+ auto j2 = R"(
+ {
+ "sensors": [],
+ "zones": [{
+ "id": 1,
+ "minThermalRpm": 3000.0,
+ "failsafePercent": 75.0,
+ "pids": [{
+ "name": "fan1-5",
+ "type": "fan",
+ "inputs": ["fan1", "fan5"],
+ "setpoint": 90.0,
+ "pid": {
+ "samplePeriod": 0.1,
+ "proportionalCoeff": 0.0,
+ "integralCoeff": 0.0,
+ "feedFwdOffOffsetCoeff": 0.0,
+ "feedFwdGainCoeff": 0.010,
+ "integralLimit_min": 0.0,
+ "integralLimit_max": 0.0,
+ "outLim_min": 30.0,
+ "outLim_max": 100.0,
+ "slewNeg": 0.0,
+ "slewPos": 0.0
+ }
+ }]
+ }]
+ }
+ )"_json;
+
+ EXPECT_THROW(validateJson(j2), ConfigurationException);
+}
+
+TEST(ConfigurationVerificationTest, VerifyNoPidInZone)
+{
+ /* Verify that there needs to be at least one PID in the zone. */
+ auto j2 = R"(
+ {
+ "sensors": [{
+ "name": "fan1",
+ "type": "fan",
+ "readPath": "/xyz/openbmc_project/sensors/fan_tach/fan1"
+ }],
+ "zones": [{
+ "id": 1,
+ "minThermalRpm": 3000.0,
+ "failsafePercent": 75.0,
+ "pids": []
+ }]
+ }
+ )"_json;
+
+ EXPECT_THROW(validateJson(j2), ConfigurationException);
+}