Save persistent triggers to storage

Create json storage file for persistent triggers.
Handle persistent dbus property.
Save/remove persistent triggers on add/delete.
Cover code with UTs.

Tested:
   - Passed unit tests
   - Tested on QEMU
     * adding new valid and invalid trigger from cli
     * verifying if valid trigger is properly stored
     * deleting existed trigger from storage

Change-Id: I243326e84833a8cb22075fbf565573b62b205b4a
Signed-off-by: Cezary Zwolak <cezary.zwolak@intel.com>
Signed-off-by: Lukasz Kazmierczak <lukasz.kazmierczak@intel.com>
diff --git a/src/trigger.cpp b/src/trigger.cpp
index 471ad10..2bba3ea 100644
--- a/src/trigger.cpp
+++ b/src/trigger.cpp
@@ -1,26 +1,44 @@
 #include "trigger.hpp"
 
 #include "interfaces/types.hpp"
+#include "utils/transform.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+template <class... Ts>
+struct overloaded : Ts...
+{
+    using Ts::operator()...;
+};
+template <class... Ts>
+overloaded(Ts...) -> overloaded<Ts...>;
 
 Trigger::Trigger(
     boost::asio::io_context& ioc,
     const std::shared_ptr<sdbusplus::asio::object_server>& objServer,
-    const std::string& nameIn, const bool isDiscrete, const bool logToJournal,
-    const bool logToRedfish, const bool updateReport,
-    const std::vector<std::pair<sdbusplus::message::object_path, std::string>>&
-        sensorsIn,
+    const std::string& nameIn, const bool isDiscreteIn,
+    const bool logToJournalIn, const bool logToRedfishIn,
+    const bool updateReportIn, const TriggerSensors& sensorsIn,
     const std::vector<std::string>& reportNamesIn,
     const TriggerThresholdParams& thresholdParamsIn,
     std::vector<std::shared_ptr<interfaces::Threshold>>&& thresholdsIn,
-    interfaces::TriggerManager& triggerManager) :
+    interfaces::TriggerManager& triggerManager,
+    interfaces::JsonStorage& triggerStorageIn) :
     name(nameIn),
-    path(triggerDir + name), persistent(false), sensors(sensorsIn),
-    reportNames(reportNamesIn), thresholdParams(thresholdParamsIn),
-    thresholds(std::move(thresholdsIn))
+    isDiscrete(isDiscreteIn), logToJournal(logToJournalIn),
+    logToRedfish(logToRedfishIn), updateReport(updateReportIn),
+    path(triggerDir + name), sensors(sensorsIn), reportNames(reportNamesIn),
+    thresholdParams(thresholdParamsIn), thresholds(std::move(thresholdsIn)),
+    fileName(std::to_string(std::hash<std::string>{}(name))),
+    triggerStorage(triggerStorageIn)
 {
     deleteIface = objServer->add_unique_interface(
         path, deleteIfaceName, [this, &ioc, &triggerManager](auto& dbusIface) {
             dbusIface.register_method("Delete", [this, &ioc, &triggerManager] {
+                if (persistent)
+                {
+                    triggerStorage.remove(fileName);
+                }
                 boost::asio::post(ioc, [this, &triggerManager] {
                     triggerManager.removeTrigger(this);
                 });
@@ -29,12 +47,30 @@
 
     triggerIface = objServer->add_unique_interface(
         path, triggerIfaceName,
-        [this, isDiscrete, logToJournal, logToRedfish,
-         updateReport](auto& dbusIface) {
-            dbusIface.register_property_r(
+        [this, isDiscreteIn, logToJournalIn, logToRedfishIn,
+         updateReportIn](auto& dbusIface) {
+            persistent = storeConfiguration();
+            dbusIface.register_property_rw(
                 "Persistent", persistent,
                 sdbusplus::vtable::property_::emits_change,
-                [](const auto& x) { return x; });
+                [this](bool newVal, const auto&) {
+                    if (newVal == persistent)
+                    {
+                        return true;
+                    }
+                    if (newVal)
+                    {
+                        persistent = storeConfiguration();
+                    }
+                    else
+                    {
+                        triggerStorage.remove(fileName);
+                        persistent = false;
+                    }
+                    return true;
+                },
+                [this](const auto&) { return persistent; });
+
             dbusIface.register_property_r(
                 "Thresholds", thresholdParams,
                 sdbusplus::vtable::property_::emits_change,
@@ -65,3 +101,62 @@
         threshold->initialize();
     }
 }
+
+bool Trigger::storeConfiguration() const
+{
+    try
+    {
+        nlohmann::json data;
+
+        data["Version"] = triggerVersion;
+        data["Name"] = name;
+        data["ThresholdParamsDiscriminator"] = thresholdParams.index();
+        data["IsDiscrete"] = isDiscrete;
+        data["LogToJournal"] = logToJournal;
+        data["LogToRedfish"] = logToRedfish;
+        data["UpdateReport"] = updateReport;
+
+        std::visit(
+            overloaded{
+                [&](const std::vector<numeric::ThresholdParam>& arg) {
+                    data["ThresholdParams"] =
+                        utils::transform(arg, [](const auto& thresholdParam) {
+                            const auto& [type, dwellTime, direction,
+                                         thresholdValue] = thresholdParam;
+                            return numeric::LabeledThresholdParam(
+                                numeric::stringToType(type), dwellTime,
+                                numeric::stringToDirection(direction),
+                                thresholdValue);
+                        });
+                },
+                [&](const std::vector<discrete::ThresholdParam>& arg) {
+                    data["ThresholdParams"] =
+                        utils::transform(arg, [](const auto& thresholdParam) {
+                            const auto& [userId, severity, dwellTime,
+                                         thresholdValue] = thresholdParam;
+                            return discrete::LabeledThresholdParam(
+                                userId, discrete::stringToSeverity(severity),
+                                dwellTime, thresholdValue);
+                        });
+                },
+            },
+            thresholdParams);
+
+        data["ReportNames"] = reportNames;
+
+        data["Sensors"] = utils::transform(sensors, [](const auto& sensor) {
+            const auto& [sensorPath, sensorMetadata] = sensor;
+            return LabeledTriggerSensor(sensorPath, sensorMetadata);
+        });
+
+        triggerStorage.store(fileName, data);
+    }
+    catch (const std::exception& e)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Failed to store a trigger in storage",
+            phosphor::logging::entry("EXCEPTION_MSG=%s", e.what()));
+        return false;
+    }
+    return true;
+}
\ No newline at end of file