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/.gitignore b/.gitignore
index eaaadc2..caa490f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -158,10 +158,6 @@
 
 ### VisualStudioCode ###
 .vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
 *.code-workspace
 
 ### VisualStudioCode Patch ###
diff --git a/src/interfaces/trigger_factory.hpp b/src/interfaces/trigger_factory.hpp
index 7d448d0..d493b4a 100644
--- a/src/interfaces/trigger_factory.hpp
+++ b/src/interfaces/trigger_factory.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include "interfaces/json_storage.hpp"
 #include "interfaces/trigger.hpp"
 #include "interfaces/trigger_manager.hpp"
 #include "interfaces/trigger_types.hpp"
@@ -26,7 +27,8 @@
             std::pair<sdbusplus::message::object_path, std::string>>& sensors,
         const std::vector<std::string>& reportNames,
         const TriggerThresholdParams& thresholdParams,
-        interfaces::TriggerManager& triggerManager) const = 0;
+        interfaces::TriggerManager& triggerManager,
+        interfaces::JsonStorage& triggerStorage) const = 0;
 };
 
 } // namespace interfaces
diff --git a/src/interfaces/trigger_types.hpp b/src/interfaces/trigger_types.hpp
index 90c673d..1833171 100644
--- a/src/interfaces/trigger_types.hpp
+++ b/src/interfaces/trigger_types.hpp
@@ -1,6 +1,8 @@
 #pragma once
 
 #include "utils/conversion.hpp"
+#include "utils/labeled_tuple.hpp"
+#include "utils/tstring.hpp"
 
 #include <string>
 #include <tuple>
@@ -38,6 +40,12 @@
 }
 
 using ThresholdParam = std::tuple<std::string, std::string, uint64_t, double>;
+
+using LabeledThresholdParam =
+    utils::LabeledTuple<std::tuple<std::string, Severity, uint64_t, double>,
+                        utils::tstring::UserId, utils::tstring::Severity,
+                        utils::tstring::DwellTime,
+                        utils::tstring::ThresholdValue>;
 } // namespace discrete
 
 namespace numeric
@@ -95,8 +103,22 @@
 }
 
 using ThresholdParam = std::tuple<std::string, uint64_t, std::string, double>;
+
+using LabeledThresholdParam =
+    utils::LabeledTuple<std::tuple<Type, uint64_t, Direction, double>,
+                        utils::tstring::Type, utils::tstring::DwellTime,
+                        utils::tstring::Direction,
+                        utils::tstring::ThresholdValue>;
 } // namespace numeric
 
+using TriggerSensors =
+    std::vector<std::pair<sdbusplus::message::object_path, std::string>>;
+
+using LabeledTriggerSensor =
+    utils::LabeledTuple<std::tuple<std::string, std::string>,
+                        utils::tstring::SensorPath,
+                        utils::tstring::SensorMetadata>;
+
 using TriggerThresholdParams =
     std::variant<std::vector<numeric::ThresholdParam>,
                  std::vector<discrete::ThresholdParam>>;
diff --git a/src/interfaces/types.hpp b/src/interfaces/types.hpp
index a5ed0db..a3a1b1c 100644
--- a/src/interfaces/types.hpp
+++ b/src/interfaces/types.hpp
@@ -28,4 +28,4 @@
 
 using Readings = std::tuple<
     uint64_t,
-    std::vector<std::tuple<std::string, std::string, double, uint64_t>>>;
+    std::vector<std::tuple<std::string, std::string, double, uint64_t>>>;
\ No newline at end of file
diff --git a/src/telemetry.hpp b/src/telemetry.hpp
index fb7f2a3..be2d81c 100644
--- a/src/telemetry.hpp
+++ b/src/telemetry.hpp
@@ -25,6 +25,9 @@
             objServer),
         triggerManager(std::make_unique<TriggerFactory>(
                            bus, objServer, sensorCache, reportManager),
+                       std::make_unique<PersistentJsonStorage>(
+                           interfaces::JsonStorage::DirectoryPath(
+                               "/var/lib/telemetry/Triggers")),
                        objServer)
     {}
 
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
diff --git a/src/trigger.hpp b/src/trigger.hpp
index 3405c5b..b2bdc1d 100644
--- a/src/trigger.hpp
+++ b/src/trigger.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include "interfaces/json_storage.hpp"
 #include "interfaces/threshold.hpp"
 #include "interfaces/trigger.hpp"
 #include "interfaces/trigger_manager.hpp"
@@ -23,7 +24,8 @@
         const std::vector<std::string>& reportNames,
         const TriggerThresholdParams& thresholdParams,
         std::vector<std::shared_ptr<interfaces::Threshold>>&& thresholds,
-        interfaces::TriggerManager& triggerManager);
+        interfaces::TriggerManager& triggerManager,
+        interfaces::JsonStorage& triggerStorage);
 
     Trigger(const Trigger&) = delete;
     Trigger(Trigger&&) = delete;
@@ -40,18 +42,26 @@
         return path;
     }
 
+    bool storeConfiguration() const;
+
   private:
     const std::string name;
+    bool isDiscrete;
+    bool logToJournal;
+    bool logToRedfish;
+    bool updateReport;
     const std::string path;
-    bool persistent;
-    std::vector<std::pair<sdbusplus::message::object_path, std::string>>
-        sensors;
+    bool persistent = false;
+    TriggerSensors sensors;
     std::vector<std::string> reportNames;
     TriggerThresholdParams thresholdParams;
     std::unique_ptr<sdbusplus::asio::dbus_interface> deleteIface;
     std::unique_ptr<sdbusplus::asio::dbus_interface> triggerIface;
     std::vector<std::shared_ptr<interfaces::Threshold>> thresholds;
 
+    interfaces::JsonStorage::FilePath fileName;
+    interfaces::JsonStorage& triggerStorage;
+
   public:
     static constexpr const char* triggerIfaceName =
         "xyz.openbmc_project.Telemetry.Trigger";
@@ -59,4 +69,5 @@
         "/xyz/openbmc_project/Telemetry/Triggers/";
     static constexpr const char* deleteIfaceName =
         "xyz.openbmc_project.Object.Delete";
+    static constexpr size_t triggerVersion = 0;
 };
diff --git a/src/trigger_factory.cpp b/src/trigger_factory.cpp
index 3114e9a..e039e1f 100644
--- a/src/trigger_factory.cpp
+++ b/src/trigger_factory.cpp
@@ -17,14 +17,15 @@
     reportManager(reportManager)
 {}
 
-std::unique_ptr<interfaces::Trigger> TriggerFactory::make(
-    boost::asio::yield_context& yield, const std::string& name, bool isDiscrete,
-    bool logToJournal, bool logToRedfish, bool updateReport,
-    const std::vector<std::pair<sdbusplus::message::object_path, std::string>>&
-        sensorPaths,
-    const std::vector<std::string>& reportNames,
-    const TriggerThresholdParams& thresholdParams,
-    interfaces::TriggerManager& triggerManager) const
+std::unique_ptr<interfaces::Trigger>
+    TriggerFactory::make(boost::asio::yield_context& yield,
+                         const std::string& name, bool isDiscrete,
+                         bool logToJournal, bool logToRedfish,
+                         bool updateReport, const TriggerSensors& sensorPaths,
+                         const std::vector<std::string>& reportNames,
+                         const TriggerThresholdParams& thresholdParams,
+                         interfaces::TriggerManager& triggerManager,
+                         interfaces::JsonStorage& triggerStorage) const
 {
     auto [sensors, sensorNames] = getSensors(yield, sensorPaths);
     std::vector<std::shared_ptr<interfaces::Threshold>> thresholds;
@@ -121,7 +122,7 @@
     return std::make_unique<Trigger>(
         bus->get_io_context(), objServer, name, isDiscrete, logToJournal,
         logToRedfish, updateReport, sensorPaths, reportNames, thresholdParams,
-        std::move(thresholds), triggerManager);
+        std::move(thresholds), triggerManager, triggerStorage);
 }
 
 std::pair<std::vector<std::shared_ptr<interfaces::Sensor>>,
diff --git a/src/trigger_factory.hpp b/src/trigger_factory.hpp
index c1f2473..a6fbb68 100644
--- a/src/trigger_factory.hpp
+++ b/src/trigger_factory.hpp
@@ -23,7 +23,8 @@
             std::pair<sdbusplus::message::object_path, std::string>>& sensors,
         const std::vector<std::string>& reportNames,
         const TriggerThresholdParams& thresholdParams,
-        interfaces::TriggerManager& triggerManager) const override;
+        interfaces::TriggerManager& triggerManager,
+        interfaces::JsonStorage& triggerStorage) const override;
 
   private:
     std::shared_ptr<sdbusplus::asio::connection> bus;
diff --git a/src/trigger_manager.cpp b/src/trigger_manager.cpp
index bf372c2..3882022 100644
--- a/src/trigger_manager.cpp
+++ b/src/trigger_manager.cpp
@@ -2,8 +2,10 @@
 
 TriggerManager::TriggerManager(
     std::unique_ptr<interfaces::TriggerFactory> triggerFactoryIn,
+    std::unique_ptr<interfaces::JsonStorage> triggerStorageIn,
     const std::shared_ptr<sdbusplus::asio::object_server>& objServer) :
-    triggerFactory(std::move(triggerFactoryIn))
+    triggerFactory(std::move(triggerFactoryIn)),
+    triggerStorage(std::move(triggerStorageIn))
 {
     managerIface = objServer->add_unique_interface(
         triggerManagerPath, triggerManagerIfaceName, [this](auto& iface) {
@@ -36,7 +38,8 @@
 
                     triggers.emplace_back(triggerFactory->make(
                         yield, name, isDiscrete, logToJournal, logToRedfish,
-                        updateReport, sensors, reportNames, thresholds, *this));
+                        updateReport, sensors, reportNames, thresholds, *this,
+                        *triggerStorage));
                     return triggers.back()->getPath();
                 });
         });
diff --git a/src/trigger_manager.hpp b/src/trigger_manager.hpp
index 41257eb..1db6d73 100644
--- a/src/trigger_manager.hpp
+++ b/src/trigger_manager.hpp
@@ -14,6 +14,7 @@
   public:
     TriggerManager(
         std::unique_ptr<interfaces::TriggerFactory> triggerFactory,
+        std::unique_ptr<interfaces::JsonStorage> triggerStorage,
         const std::shared_ptr<sdbusplus::asio::object_server>& objServer);
 
     TriggerManager(TriggerManager&) = delete;
@@ -25,6 +26,7 @@
 
   private:
     std::unique_ptr<interfaces::TriggerFactory> triggerFactory;
+    std::unique_ptr<interfaces::JsonStorage> triggerStorage;
     std::unique_ptr<sdbusplus::asio::dbus_interface> managerIface;
     std::vector<std::unique_ptr<interfaces::Trigger>> triggers;
 
diff --git a/src/utils/tstring.hpp b/src/utils/tstring.hpp
index a8d3e90..5b17d98 100644
--- a/src/utils/tstring.hpp
+++ b/src/utils/tstring.hpp
@@ -24,6 +24,14 @@
     }
 };
 
+struct SensorMetadata
+{
+    static std::string str()
+    {
+        return "sensorMetadata";
+    }
+};
+
 struct OperationType
 {
     static std::string str()
@@ -56,5 +64,53 @@
     }
 };
 
+struct Type
+{
+    static std::string str()
+    {
+        return "type";
+    }
+};
+
+struct DwellTime
+{
+    static std::string str()
+    {
+        return "dwellTime";
+    }
+};
+
+struct Direction
+{
+    static std::string str()
+    {
+        return "direction";
+    }
+};
+
+struct ThresholdValue
+{
+    static std::string str()
+    {
+        return "thresholdValue";
+    }
+};
+
+struct UserId
+{
+    static std::string str()
+    {
+        return "userId";
+    }
+};
+
+struct Severity
+{
+    static std::string str()
+    {
+        return "severity";
+    }
+};
+
 } // namespace tstring
 } // namespace utils
diff --git a/tests/src/mocks/trigger_factory_mock.hpp b/tests/src/mocks/trigger_factory_mock.hpp
index cf1be55..8249799 100644
--- a/tests/src/mocks/trigger_factory_mock.hpp
+++ b/tests/src/mocks/trigger_factory_mock.hpp
@@ -13,7 +13,7 @@
     {
         using namespace testing;
 
-        ON_CALL(*this, make(_, _, _, _, _, _, _, _, _, _))
+        ON_CALL(*this, make(_, _, _, _, _, _, _, _, _, _, _))
             .WillByDefault(WithArgs<1>(Invoke([](const std::string& name) {
                 return std::make_unique<NiceMock<TriggerMock>>(name);
             })));
@@ -27,12 +27,14 @@
              std::pair<sdbusplus::message::object_path, std::string>>& sensors),
          const std::vector<std::string>& reportNames,
          const TriggerThresholdParams& thresholdParams,
-         interfaces::TriggerManager& triggerManager),
+         interfaces::TriggerManager& triggerManager,
+         interfaces::JsonStorage& triggerStorage),
         (const, override));
 
     auto& expectMake(
         std::optional<std::reference_wrapper<const TriggerParams>> paramsOpt,
-        const testing::Matcher<interfaces::TriggerManager&>& tm)
+        const testing::Matcher<interfaces::TriggerManager&>& tm,
+        const testing::Matcher<interfaces::JsonStorage&>& triggerStorage)
     {
         using namespace testing;
 
@@ -40,15 +42,16 @@
         {
             const TriggerParams& params = *paramsOpt;
             return EXPECT_CALL(
-                *this,
-                make(_, params.name(), params.isDiscrete(),
-                     params.logToJournal(), params.logToRedfish(),
-                     params.updateReport(), params.sensors(),
-                     params.reportNames(), params.thresholdParams(), tm));
+                *this, make(_, params.name(), params.isDiscrete(),
+                            params.logToJournal(), params.logToRedfish(),
+                            params.updateReport(), params.sensors(),
+                            params.reportNames(), params.thresholdParams(), tm,
+                            triggerStorage));
         }
         else
         {
-            return EXPECT_CALL(*this, make(_, _, _, _, _, _, _, _, _, tm));
+            return EXPECT_CALL(
+                *this, make(_, _, _, _, _, _, _, _, _, tm, triggerStorage));
         }
     }
 };
diff --git a/tests/src/params/trigger_params.hpp b/tests/src/params/trigger_params.hpp
index 95b19d2..08bed4b 100644
--- a/tests/src/params/trigger_params.hpp
+++ b/tests/src/params/trigger_params.hpp
@@ -42,13 +42,18 @@
         return logToRedfishProperty;
     }
 
+    TriggerParams& updateReport(bool updateReport)
+    {
+        updateReportProperty = updateReport;
+        return *this;
+    }
+
     bool updateReport() const
     {
         return updateReportProperty;
     }
 
-    const std::vector<std::pair<sdbusplus::message::object_path, std::string>>&
-        sensors() const
+    const TriggerSensors& sensors() const
     {
         return sensorsProperty;
     }
@@ -74,12 +79,11 @@
     bool discreteProperty = false;
     bool logToJournalProperty = false;
     bool logToRedfishProperty = false;
-    bool updateReportProperty = false;
-    std::vector<std::pair<sdbusplus::message::object_path, std::string>>
-        sensorsProperty = {
-            {sdbusplus::message::object_path(
-                 "/xyz/openbmc_project/sensors/temperature/BMC_Temp"),
-             ""}};
+    bool updateReportProperty = true;
+    TriggerSensors sensorsProperty = {
+        {sdbusplus::message::object_path(
+             "/xyz/openbmc_project/sensors/temperature/BMC_Temp"),
+         ""}};
     std::vector<std::string> reportNamesProperty = {"Report1"};
     TriggerThresholdParams thresholdsProperty =
         std::vector<numeric::ThresholdParam>{
diff --git a/tests/src/test_trigger.cpp b/tests/src/test_trigger.cpp
index b2b259c..f396589 100644
--- a/tests/src/test_trigger.cpp
+++ b/tests/src/test_trigger.cpp
@@ -1,5 +1,6 @@
 #include "dbus_environment.hpp"
 #include "helpers.hpp"
+#include "mocks/json_storage_mock.hpp"
 #include "mocks/trigger_manager_mock.hpp"
 #include "params/trigger_params.hpp"
 #include "trigger.hpp"
@@ -8,6 +9,8 @@
 using namespace testing;
 using namespace std::literals::string_literals;
 
+static constexpr size_t expectedTriggerVersion = 0;
+
 class TestTrigger : public Test
 {
   public:
@@ -15,18 +18,29 @@
 
     std::unique_ptr<TriggerManagerMock> triggerManagerMockPtr =
         std::make_unique<NiceMock<TriggerManagerMock>>();
+    testing::NiceMock<StorageMock> storageMock;
     std::unique_ptr<Trigger> sut;
 
     void SetUp() override
     {
-        sut = std::make_unique<Trigger>(
+        sut = makeTrigger(triggerParams);
+    }
+
+    std::unique_ptr<Trigger> makeTrigger(const TriggerParams& params)
+    {
+        return std::make_unique<Trigger>(
             DbusEnvironment::getIoc(), DbusEnvironment::getObjServer(),
-            triggerParams.name(), triggerParams.isDiscrete(),
-            triggerParams.logToJournal(), triggerParams.logToRedfish(),
-            triggerParams.updateReport(), triggerParams.sensors(),
-            triggerParams.reportNames(), triggerParams.thresholdParams(),
+            params.name(), params.isDiscrete(), params.logToJournal(),
+            params.logToRedfish(), params.updateReport(), params.sensors(),
+            params.reportNames(), params.thresholdParams(),
             std::vector<std::shared_ptr<interfaces::Threshold>>{},
-            *triggerManagerMockPtr);
+            *triggerManagerMockPtr, storageMock);
+    }
+
+    static interfaces::JsonStorage::FilePath to_file_path(std::string name)
+    {
+        return interfaces::JsonStorage::FilePath(
+            std::to_string(std::hash<std::string>{}(name)));
     }
 
     template <class T>
@@ -48,6 +62,24 @@
         return DbusEnvironment::waitForFuture(std::move(propertyFuture));
     }
 
+    template <class T>
+    static boost::system::error_code setProperty(const std::string& path,
+                                                 const std::string& property,
+                                                 const T& newValue)
+    {
+        auto setPromise = std::promise<boost::system::error_code>();
+        auto setFuture = setPromise.get_future();
+
+        sdbusplus::asio::setProperty(
+            *DbusEnvironment::getBus(), DbusEnvironment::serviceName(), path,
+            Trigger::triggerIfaceName, property, std::move(newValue),
+            [setPromise =
+                 std::move(setPromise)](boost::system::error_code ec) mutable {
+                setPromise.set_value(ec);
+            });
+        return DbusEnvironment::waitForFuture(std::move(setFuture));
+    }
+
     boost::system::error_code deleteTrigger(const std::string& path)
     {
         std::promise<boost::system::error_code> methodPromise;
@@ -63,6 +95,7 @@
 
 TEST_F(TestTrigger, checkIfPropertiesAreSet)
 {
+    EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistent"), Eq(true));
     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Discrete"),
                 Eq(triggerParams.isDiscrete()));
     EXPECT_THAT(getProperty<bool>(sut->getPath(), "LogToJournal"),
@@ -85,6 +118,7 @@
 
 TEST_F(TestTrigger, deleteTrigger)
 {
+    EXPECT_CALL(storageMock, remove(to_file_path(sut->getName())));
     EXPECT_CALL(*triggerManagerMockPtr, removeTrigger(sut.get()));
     auto ec = deleteTrigger(sut->getPath());
     EXPECT_THAT(ec, Eq(boost::system::errc::success));
@@ -95,3 +129,125 @@
     auto ec = deleteTrigger(Trigger::triggerDir + "NonExisting"s);
     EXPECT_THAT(ec.value(), Eq(EBADR));
 }
+
+TEST_F(TestTrigger, settingPersistencyToFalseRemovesReportFromStorage)
+{
+    EXPECT_CALL(storageMock, remove(to_file_path(sut->getName())));
+
+    bool persistent = false;
+    EXPECT_THAT(setProperty(sut->getPath(), "Persistent", persistent).value(),
+                Eq(boost::system::errc::success));
+    EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistent"),
+                Eq(persistent));
+}
+
+class TestTriggerErrors : public TestTrigger
+{
+  public:
+    void SetUp() override
+    {}
+
+    nlohmann::json storedConfiguration;
+};
+
+TEST_F(TestTriggerErrors, throwingExceptionDoesNotStoreTriggerReportNames)
+{
+    EXPECT_CALL(storageMock, store(_, _))
+        .WillOnce(Throw(std::runtime_error("Generic error!")));
+
+    sut = makeTrigger(triggerParams);
+
+    EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistent"), Eq(false));
+}
+
+TEST_F(TestTriggerErrors, creatingTriggerThrowsExceptionWhenNameIsInvalid)
+{
+    EXPECT_CALL(storageMock, store(_, _)).Times(0);
+
+    EXPECT_THROW(makeTrigger(triggerParams.name("inv?lidName")),
+                 sdbusplus::exception::SdBusError);
+}
+
+class TestTriggerStore : public TestTrigger
+{
+  public:
+    void SetUp() override
+    {
+        ON_CALL(storageMock, store(_, _))
+            .WillByDefault(SaveArg<1>(&storedConfiguration));
+
+        sut = makeTrigger(triggerParams);
+    }
+
+    nlohmann::json storedConfiguration;
+};
+
+TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerVersion)
+{
+    ASSERT_THAT(storedConfiguration.at("Version"), Eq(expectedTriggerVersion));
+}
+
+TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerName)
+{
+    ASSERT_THAT(storedConfiguration.at("Name"), Eq(triggerParams.name()));
+}
+
+TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerIsDiscrete)
+{
+    ASSERT_THAT(storedConfiguration.at("IsDiscrete"),
+                Eq(triggerParams.isDiscrete()));
+}
+
+TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerLogToJournal)
+{
+    ASSERT_THAT(storedConfiguration.at("LogToJournal"),
+                Eq(triggerParams.logToRedfish()));
+}
+
+TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerLogToRedfish)
+{
+    ASSERT_THAT(storedConfiguration.at("LogToRedfish"),
+                Eq(triggerParams.logToRedfish()));
+}
+
+TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerUpdateReport)
+{
+    ASSERT_THAT(storedConfiguration.at("UpdateReport"),
+                Eq(triggerParams.updateReport()));
+}
+
+TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerReportNames)
+{
+    ASSERT_THAT(storedConfiguration.at("ReportNames"),
+                Eq(triggerParams.reportNames()));
+}
+
+TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerSensors)
+{
+    nlohmann::json expectedItem;
+    expectedItem["sensorPath"] =
+        "/xyz/openbmc_project/sensors/temperature/BMC_Temp";
+    expectedItem["sensorMetadata"] = "";
+
+    ASSERT_THAT(storedConfiguration.at("Sensors"), ElementsAre(expectedItem));
+}
+
+TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerThresholdParams)
+{
+    ASSERT_THAT(storedConfiguration.at("ThresholdParamsDiscriminator"), Eq(0));
+
+    nlohmann::json expectedItem0;
+    expectedItem0["type"] = 0;
+    expectedItem0["dwellTime"] = 10;
+    expectedItem0["direction"] = 1;
+    expectedItem0["thresholdValue"] = 0.0;
+
+    nlohmann::json expectedItem1;
+    expectedItem1["type"] = 3;
+    expectedItem1["dwellTime"] = 10;
+    expectedItem1["direction"] = 2;
+    expectedItem1["thresholdValue"] = 90.0;
+
+    ASSERT_THAT(storedConfiguration.at("ThresholdParams"),
+                ElementsAre(expectedItem0, expectedItem1));
+}
diff --git a/tests/src/test_trigger_manager.cpp b/tests/src/test_trigger_manager.cpp
index 8754446..1bc4f1b 100644
--- a/tests/src/test_trigger_manager.cpp
+++ b/tests/src/test_trigger_manager.cpp
@@ -1,5 +1,6 @@
 #include "dbus_environment.hpp"
 #include "helpers.hpp"
+#include "mocks/json_storage_mock.hpp"
 #include "mocks/trigger_factory_mock.hpp"
 #include "mocks/trigger_mock.hpp"
 #include "params/trigger_params.hpp"
@@ -29,6 +30,9 @@
     }
 
     TriggerParams triggerParams;
+    std::unique_ptr<StorageMock> storageMockPtr =
+        std::make_unique<NiceMock<StorageMock>>();
+    StorageMock& storageMock = *storageMockPtr;
     std::unique_ptr<TriggerFactoryMock> triggerFactoryMockPtr =
         std::make_unique<NiceMock<TriggerFactoryMock>>();
     TriggerFactoryMock& triggerFactoryMock = *triggerFactoryMockPtr;
@@ -36,14 +40,14 @@
         std::make_unique<NiceMock<TriggerMock>>(triggerParams.name());
     TriggerMock& triggerMock = *triggerMockPtr;
     std::unique_ptr<TriggerManager> sut = std::make_unique<TriggerManager>(
-        std::move(triggerFactoryMockPtr),
+        std::move(triggerFactoryMockPtr), std::move(storageMockPtr),
         std::move(DbusEnvironment::getObjServer()));
     MockFunction<void(std::string)> checkPoint;
 };
 
 TEST_F(TestTriggerManager, addTrigger)
 {
-    triggerFactoryMock.expectMake(triggerParams, Ref(*sut))
+    triggerFactoryMock.expectMake(triggerParams, Ref(*sut), Ref(storageMock))
         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
 
     auto [ec, path] = addTrigger(triggerParams);
@@ -83,7 +87,7 @@
 
 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerTwice)
 {
-    triggerFactoryMock.expectMake(triggerParams, Ref(*sut))
+    triggerFactoryMock.expectMake(triggerParams, Ref(*sut), Ref(storageMock))
         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
 
     addTrigger(triggerParams);
@@ -95,7 +99,7 @@
 
 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWhenMaxTriggerIsReached)
 {
-    triggerFactoryMock.expectMake(std::nullopt, Ref(*sut))
+    triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
         .Times(TriggerManager::maxTriggers);
 
     for (size_t i = 0; i < TriggerManager::maxTriggers; i++)
@@ -117,7 +121,8 @@
 {
     {
         InSequence seq;
-        triggerFactoryMock.expectMake(triggerParams, Ref(*sut))
+        triggerFactoryMock
+            .expectMake(triggerParams, Ref(*sut), Ref(storageMock))
             .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
         EXPECT_CALL(triggerMock, Die());
         EXPECT_CALL(checkPoint, Call("end"));
@@ -144,7 +149,8 @@
 {
     {
         InSequence seq;
-        triggerFactoryMock.expectMake(triggerParams, Ref(*sut))
+        triggerFactoryMock
+            .expectMake(triggerParams, Ref(*sut), Ref(storageMock))
             .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
         EXPECT_CALL(triggerMock, Die());
         EXPECT_CALL(checkPoint, Call("end"));