Read persistent triggers from storage

Read json storage file for persistent triggers.
Add trigger to telemetry service.
Cover code with UTs.

Tested:
  - Passed unit tests
  - Tested on QEMU
    * starting app without configuration
    * restart app with configuration stored
    * restart app with configuration in incorrect version
    * restart app with configuration malformed

Change-Id: I2cb9324abdb8323be8a7f0c932ed7f70c5bc2891
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 caa490f..6ea24d5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
 xyz.openbmc_project.Telemetry.service
 telemetry
 __pycache__
+*.bak
 
 # Created by https://www.toptal.com/developers/gitignore/api/vim,intellij,meson,visualstudiocode
 # Edit at https://www.toptal.com/developers/gitignore?templates=vim,intellij,meson,visualstudiocode
@@ -165,4 +166,9 @@
 .history
 .ionide
 
+### Eclipse IDE ###
+.cproject
+.project
+.settings/*
+
 # End of https://www.toptal.com/developers/gitignore/api/vim,intellij,meson,visualstudiocode
diff --git a/meson.build b/meson.build
index 145805a..2283e20 100644
--- a/meson.build
+++ b/meson.build
@@ -88,6 +88,7 @@
         'src/trigger_actions.cpp',
         'src/trigger_factory.cpp',
         'src/trigger_manager.cpp',
+        'src/utils/conversion_trigger.cpp',
     ],
     dependencies: [
         boost,
diff --git a/src/interfaces/trigger_factory.hpp b/src/interfaces/trigger_factory.hpp
index d493b4a..b84ce73 100644
--- a/src/interfaces/trigger_factory.hpp
+++ b/src/interfaces/trigger_factory.hpp
@@ -20,15 +20,17 @@
     virtual ~TriggerFactory() = default;
 
     virtual std::unique_ptr<interfaces::Trigger> 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>>& sensors,
+        const std::string& name, bool isDiscrete, bool logToJournal,
+        bool logToRedfish, bool updateReport,
         const std::vector<std::string>& reportNames,
-        const TriggerThresholdParams& thresholdParams,
         interfaces::TriggerManager& triggerManager,
-        interfaces::JsonStorage& triggerStorage) const = 0;
+        interfaces::JsonStorage& triggerStorage,
+        const LabeledTriggerThresholdParams& labeledThresholdParams,
+        const std::vector<LabeledSensorInfo>& labeledSensorsInfo) const = 0;
+
+    virtual std::vector<LabeledSensorInfo>
+        getLabeledSensorsInfo(boost::asio::yield_context& yield,
+                              const SensorsInfo& sensorsInfo) const = 0;
 };
 
 } // namespace interfaces
diff --git a/src/interfaces/trigger_types.hpp b/src/interfaces/trigger_types.hpp
index 1833171..568756f 100644
--- a/src/interfaces/trigger_types.hpp
+++ b/src/interfaces/trigger_types.hpp
@@ -111,14 +111,22 @@
                         utils::tstring::ThresholdValue>;
 } // namespace numeric
 
-using TriggerSensors =
+using SensorsInfo =
     std::vector<std::pair<sdbusplus::message::object_path, std::string>>;
 
-using LabeledTriggerSensor =
-    utils::LabeledTuple<std::tuple<std::string, std::string>,
-                        utils::tstring::SensorPath,
+using LabeledSensorInfo =
+    utils::LabeledTuple<std::tuple<std::string, std::string, std::string>,
+                        utils::tstring::Service, utils::tstring::SensorPath,
                         utils::tstring::SensorMetadata>;
 
+using TriggerThresholdParamsExt =
+    std::variant<std::monostate, std::vector<numeric::ThresholdParam>,
+                 std::vector<discrete::ThresholdParam>>;
+
 using TriggerThresholdParams =
     std::variant<std::vector<numeric::ThresholdParam>,
                  std::vector<discrete::ThresholdParam>>;
+
+using LabeledTriggerThresholdParams =
+    std::variant<std::vector<numeric::LabeledThresholdParam>,
+                 std::vector<discrete::LabeledThresholdParam>>;
diff --git a/src/trigger.cpp b/src/trigger.cpp
index 2bba3ea..6289a05 100644
--- a/src/trigger.cpp
+++ b/src/trigger.cpp
@@ -1,34 +1,30 @@
 #include "trigger.hpp"
 
+#include "interfaces/trigger_types.hpp"
 #include "interfaces/types.hpp"
+#include "utils/conversion_trigger.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 isDiscreteIn,
     const bool logToJournalIn, const bool logToRedfishIn,
-    const bool updateReportIn, const TriggerSensors& sensorsIn,
-    const std::vector<std::string>& reportNamesIn,
-    const TriggerThresholdParams& thresholdParamsIn,
+    const bool updateReportIn, const std::vector<std::string>& reportNamesIn,
+    const std::vector<LabeledSensorInfo>& LabeledSensorsInfoIn,
+    const LabeledTriggerThresholdParams& labeledThresholdParamsIn,
     std::vector<std::shared_ptr<interfaces::Threshold>>&& thresholdsIn,
     interfaces::TriggerManager& triggerManager,
     interfaces::JsonStorage& triggerStorageIn) :
     name(nameIn),
     isDiscrete(isDiscreteIn), logToJournal(logToJournalIn),
     logToRedfish(logToRedfishIn), updateReport(updateReportIn),
-    path(triggerDir + name), sensors(sensorsIn), reportNames(reportNamesIn),
-    thresholdParams(thresholdParamsIn), thresholds(std::move(thresholdsIn)),
+    path(triggerDir + name), reportNames(reportNamesIn),
+    labeledSensorsInfo(LabeledSensorsInfoIn),
+    labeledThresholdParams(labeledThresholdParamsIn),
+    thresholds(std::move(thresholdsIn)),
     fileName(std::to_string(std::hash<std::string>{}(name))),
     triggerStorage(triggerStorageIn)
 {
@@ -72,13 +68,23 @@
                 [this](const auto&) { return persistent; });
 
             dbusIface.register_property_r(
-                "Thresholds", thresholdParams,
+                "Thresholds", TriggerThresholdParams{},
                 sdbusplus::vtable::property_::emits_change,
-                [](const auto& x) { return x; });
+                [this](const auto&) {
+                    return std::visit(
+                        utils::FromLabeledThresholdParamConversion(),
+                        labeledThresholdParams);
+                });
+
             dbusIface.register_property_r(
-                "Sensors", sensors, sdbusplus::vtable::property_::emits_change,
-                [](const auto& x) { return x; });
+                "Sensors", SensorsInfo{},
+                sdbusplus::vtable::property_::emits_change,
+                [this](const auto&) {
+                    return utils::fromLabeledSensorsInfo(labeledSensorsInfo);
+                });
+
             dbusIface.register_property_r(
+
                 "ReportNames", reportNames,
                 sdbusplus::vtable::property_::emits_change,
                 [](const auto& x) { return x; });
@@ -110,44 +116,15 @@
 
         data["Version"] = triggerVersion;
         data["Name"] = name;
-        data["ThresholdParamsDiscriminator"] = thresholdParams.index();
-        data["IsDiscrete"] = isDiscrete;
+        data["ThresholdParamsDiscriminator"] = labeledThresholdParams.index();
+        data["IsDiscrete"] = labeledThresholdParams.index() == 1;
         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["ThresholdParams"] =
+            utils::labeledThresholdParamsToJson(labeledThresholdParams);
         data["ReportNames"] = reportNames;
-
-        data["Sensors"] = utils::transform(sensors, [](const auto& sensor) {
-            const auto& [sensorPath, sensorMetadata] = sensor;
-            return LabeledTriggerSensor(sensorPath, sensorMetadata);
-        });
+        data["Sensors"] = labeledSensorsInfo;
 
         triggerStorage.store(fileName, data);
     }
@@ -159,4 +136,4 @@
         return false;
     }
     return true;
-}
\ No newline at end of file
+}
diff --git a/src/trigger.hpp b/src/trigger.hpp
index b2bdc1d..a108caf 100644
--- a/src/trigger.hpp
+++ b/src/trigger.hpp
@@ -14,18 +14,17 @@
 class Trigger : public interfaces::Trigger
 {
   public:
-    Trigger(
-        boost::asio::io_context& ioc,
-        const std::shared_ptr<sdbusplus::asio::object_server>& objServer,
-        const std::string& name, 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::vector<std::string>& reportNames,
-        const TriggerThresholdParams& thresholdParams,
-        std::vector<std::shared_ptr<interfaces::Threshold>>&& thresholds,
-        interfaces::TriggerManager& triggerManager,
-        interfaces::JsonStorage& triggerStorage);
+    Trigger(boost::asio::io_context& ioc,
+            const std::shared_ptr<sdbusplus::asio::object_server>& objServer,
+            const std::string& name, const bool isDiscrete,
+            const bool logToJournal, const bool logToRedfish,
+            const bool updateReport,
+            const std::vector<std::string>& reportNames,
+            const std::vector<LabeledSensorInfo>& LabeledSensorsInfoIn,
+            const LabeledTriggerThresholdParams& labeledThresholdParamsIn,
+            std::vector<std::shared_ptr<interfaces::Threshold>>&& thresholds,
+            interfaces::TriggerManager& triggerManager,
+            interfaces::JsonStorage& triggerStorage);
 
     Trigger(const Trigger&) = delete;
     Trigger(Trigger&&) = delete;
@@ -52,9 +51,9 @@
     bool updateReport;
     const std::string path;
     bool persistent = false;
-    TriggerSensors sensors;
     std::vector<std::string> reportNames;
-    TriggerThresholdParams thresholdParams;
+    std::vector<LabeledSensorInfo> labeledSensorsInfo;
+    LabeledTriggerThresholdParams labeledThresholdParams;
     std::unique_ptr<sdbusplus::asio::dbus_interface> deleteIface;
     std::unique_ptr<sdbusplus::asio::dbus_interface> triggerIface;
     std::vector<std::shared_ptr<interfaces::Threshold>> thresholds;
diff --git a/src/trigger_factory.cpp b/src/trigger_factory.cpp
index e039e1f..b491274 100644
--- a/src/trigger_factory.cpp
+++ b/src/trigger_factory.cpp
@@ -7,6 +7,9 @@
 #include "trigger.hpp"
 #include "trigger_actions.hpp"
 #include "utils/dbus_mapper.hpp"
+#include "utils/transform.hpp"
+
+namespace ts = utils::tstring;
 
 TriggerFactory::TriggerFactory(
     std::shared_ptr<sdbusplus::asio::connection> bus,
@@ -17,29 +20,36 @@
     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 TriggerSensors& sensorPaths,
-                         const std::vector<std::string>& reportNames,
-                         const TriggerThresholdParams& thresholdParams,
-                         interfaces::TriggerManager& triggerManager,
-                         interfaces::JsonStorage& triggerStorage) const
+std::unique_ptr<interfaces::Trigger> TriggerFactory::make(
+    const std::string& name, bool isDiscrete, bool logToJournal,
+    bool logToRedfish, bool updateReport,
+    const std::vector<std::string>& reportNames,
+    interfaces::TriggerManager& triggerManager,
+    interfaces::JsonStorage& triggerStorage,
+    const LabeledTriggerThresholdParams& labeledThresholdParams,
+    const std::vector<LabeledSensorInfo>& labeledSensorsInfo) const
 {
-    auto [sensors, sensorNames] = getSensors(yield, sensorPaths);
+    const auto& [sensors, sensorNames] = getSensors(labeledSensorsInfo);
     std::vector<std::shared_ptr<interfaces::Threshold>> thresholds;
 
     if (isDiscrete)
     {
-        const auto& params =
-            std::get<std::vector<discrete::ThresholdParam>>(thresholdParams);
-        for (const auto& [thresholdName, severityStr, dwellTime,
-                          thresholdValue] : params)
+        const auto& labeledDiscreteThresholdParams =
+            std::get<std::vector<discrete::LabeledThresholdParam>>(
+                labeledThresholdParams);
+        for (const auto& labeledThresholdParam : labeledDiscreteThresholdParams)
         {
-            discrete::Severity severity =
-                discrete::stringToSeverity(severityStr);
             std::vector<std::unique_ptr<interfaces::TriggerAction>> actions;
+
+            std::string thresholdName =
+                labeledThresholdParam.at_label<ts::UserId>();
+            discrete::Severity severity =
+                labeledThresholdParam.at_label<ts::Severity>();
+            std::chrono::milliseconds dwellTime = std::chrono::milliseconds(
+                labeledThresholdParam.at_label<ts::DwellTime>());
+            double thresholdValue =
+                labeledThresholdParam.at_label<ts::ThresholdValue>();
+
             if (logToJournal)
             {
                 actions.emplace_back(
@@ -61,7 +71,7 @@
                 std::chrono::milliseconds(dwellTime), thresholdValue,
                 thresholdName));
         }
-        if (params.empty())
+        if (labeledDiscreteThresholdParams.empty())
         {
             std::vector<std::unique_ptr<interfaces::TriggerAction>> actions;
             if (logToJournal)
@@ -88,24 +98,34 @@
     }
     else
     {
-        const auto& params =
-            std::get<std::vector<numeric::ThresholdParam>>(thresholdParams);
-        for (const auto& [typeStr, dwellTime, directionStr, value] : params)
+        const auto& labeledNumericThresholdParams =
+            std::get<std::vector<numeric::LabeledThresholdParam>>(
+                labeledThresholdParams);
+
+        for (const auto& labeledThresholdParam : labeledNumericThresholdParams)
         {
-            numeric::Type type = numeric::stringToType(typeStr);
             std::vector<std::unique_ptr<interfaces::TriggerAction>> actions;
+            auto type = labeledThresholdParam.at_label<ts::Type>();
+            auto dwellTime = std::chrono::milliseconds(
+                labeledThresholdParam.at_label<ts::DwellTime>());
+            auto direction = labeledThresholdParam.at_label<ts::Direction>();
+            auto thresholdValue =
+                double{labeledThresholdParam.at_label<ts::ThresholdValue>()};
+
             if (logToJournal)
             {
                 actions.emplace_back(
-                    std::make_unique<action::numeric::LogToJournal>(type,
-                                                                    value));
+                    std::make_unique<action::numeric::LogToJournal>(
+                        type, thresholdValue));
             }
+
             if (logToRedfish)
             {
                 actions.emplace_back(
-                    std::make_unique<action::numeric::LogToRedfish>(type,
-                                                                    value));
+                    std::make_unique<action::numeric::LogToRedfish>(
+                        type, thresholdValue));
             }
+
             if (updateReport)
             {
                 actions.emplace_back(std::make_unique<action::UpdateReport>(
@@ -114,42 +134,34 @@
 
             thresholds.emplace_back(std::make_shared<NumericThreshold>(
                 bus->get_io_context(), sensors, sensorNames, std::move(actions),
-                std::chrono::milliseconds(dwellTime),
-                numeric::stringToDirection(directionStr), value));
+                dwellTime, direction, thresholdValue));
         }
     }
 
     return std::make_unique<Trigger>(
         bus->get_io_context(), objServer, name, isDiscrete, logToJournal,
-        logToRedfish, updateReport, sensorPaths, reportNames, thresholdParams,
-        std::move(thresholds), triggerManager, triggerStorage);
+        logToRedfish, updateReport, reportNames, labeledSensorsInfo,
+        labeledThresholdParams, std::move(thresholds), triggerManager,
+        triggerStorage);
 }
 
 std::pair<std::vector<std::shared_ptr<interfaces::Sensor>>,
           std::vector<std::string>>
     TriggerFactory::getSensors(
-        boost::asio::yield_context& yield,
-        const std::vector<std::pair<sdbusplus::message::object_path,
-                                    std::string>>& sensorPaths) const
+        const std::vector<LabeledSensorInfo>& labeledSensorsInfo) const
 {
-    auto tree = utils::getSubTreeSensors(yield, bus);
-
     std::vector<std::shared_ptr<interfaces::Sensor>> sensors;
     std::vector<std::string> sensorNames;
-    for (const auto& [sensorPath, metadata] : sensorPaths)
-    {
-        auto found = std::find_if(
-            tree.begin(), tree.end(),
-            [&sensorPath](const auto& x) { return x.first == sensorPath; });
-        if (found == tree.end())
-        {
-            throw std::runtime_error("Not found");
-        }
 
-        const auto& service = found->second[0].first;
-        const auto& path = found->first;
+    for (const auto& labeledSensorInfo : labeledSensorsInfo)
+    {
+        const auto& service = labeledSensorInfo.at_label<ts::Service>();
+        const auto& sensorPath = labeledSensorInfo.at_label<ts::SensorPath>();
+        const auto& metadata = labeledSensorInfo.at_label<ts::SensorMetadata>();
+
         sensors.emplace_back(sensorCache.makeSensor<Sensor>(
-            service, path, bus->get_io_context(), bus));
+            service, sensorPath, bus->get_io_context(), bus));
+
         if (metadata.empty())
         {
             sensorNames.emplace_back(sensorPath);
@@ -159,5 +171,27 @@
             sensorNames.emplace_back(metadata);
         }
     }
+
     return {sensors, sensorNames};
 }
+
+std::vector<LabeledSensorInfo>
+    TriggerFactory::getLabeledSensorsInfo(boost::asio::yield_context& yield,
+                                          const SensorsInfo& sensorsInfo) const
+{
+    auto tree = utils::getSubTreeSensors(yield, bus);
+
+    return utils::transform(sensorsInfo, [&tree](const auto& item) {
+        const auto& [sensorPath, metadata] = item;
+        auto found = std::find_if(
+            tree.begin(), tree.end(),
+            [&sensorPath](const auto& x) { return x.first == sensorPath; });
+
+        if (tree.end() != found)
+        {
+            const auto& [service, ifaces] = found->second.front();
+            return LabeledSensorInfo(service, sensorPath, metadata);
+        }
+        throw std::runtime_error("Not found");
+    });
+}
diff --git a/src/trigger_factory.hpp b/src/trigger_factory.hpp
index a6fbb68..7c13e55 100644
--- a/src/trigger_factory.hpp
+++ b/src/trigger_factory.hpp
@@ -15,16 +15,19 @@
                    SensorCache& sensorCache,
                    interfaces::ReportManager& reportManager);
 
-    std::unique_ptr<interfaces::Trigger> 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>>& sensors,
-        const std::vector<std::string>& reportNames,
-        const TriggerThresholdParams& thresholdParams,
-        interfaces::TriggerManager& triggerManager,
-        interfaces::JsonStorage& triggerStorage) const override;
+    std::unique_ptr<interfaces::Trigger>
+        make(const std::string& name, bool isDiscrete, bool logToJournal,
+             bool logToRedfish, bool updateReport,
+             const std::vector<std::string>& reportNames,
+             interfaces::TriggerManager& triggerManager,
+             interfaces::JsonStorage& triggerStorage,
+             const LabeledTriggerThresholdParams& labeledThresholdParams,
+             const std::vector<LabeledSensorInfo>& labeledSensorsinfo)
+            const override;
+
+    std::vector<LabeledSensorInfo>
+        getLabeledSensorsInfo(boost::asio::yield_context& yield,
+                              const SensorsInfo& sensorsInfo) const;
 
   private:
     std::shared_ptr<sdbusplus::asio::connection> bus;
@@ -34,8 +37,6 @@
 
     std::pair<std::vector<std::shared_ptr<interfaces::Sensor>>,
               std::vector<std::string>>
-        getSensors(boost::asio::yield_context& yield,
-                   const std::vector<
-                       std::pair<sdbusplus::message::object_path, std::string>>&
-                       sensorPaths) const;
+        getSensors(
+            const std::vector<LabeledSensorInfo>& labeledSensorsInfo) const;
 };
diff --git a/src/trigger_manager.cpp b/src/trigger_manager.cpp
index 3882022..d9d04d2 100644
--- a/src/trigger_manager.cpp
+++ b/src/trigger_manager.cpp
@@ -1,5 +1,12 @@
 #include "trigger_manager.hpp"
 
+#include "interfaces/trigger_types.hpp"
+#include "trigger.hpp"
+#include "utils/conversion_trigger.hpp"
+#include "utils/transform.hpp"
+
+#include <phosphor-logging/log.hpp>
+
 TriggerManager::TriggerManager(
     std::unique_ptr<interfaces::TriggerFactory> triggerFactoryIn,
     std::unique_ptr<interfaces::JsonStorage> triggerStorageIn,
@@ -7,40 +14,31 @@
     triggerFactory(std::move(triggerFactoryIn)),
     triggerStorage(std::move(triggerStorageIn))
 {
+    loadFromPersistent();
+
     managerIface = objServer->add_unique_interface(
         triggerManagerPath, triggerManagerIfaceName, [this](auto& iface) {
             iface.register_method(
                 "AddTrigger",
-                [this](
-                    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>>& sensors,
-                    const std::vector<std::string>& reportNames,
-                    const TriggerThresholdParams& thresholds) {
-                    if (triggers.size() >= maxTriggers)
-                    {
-                        throw sdbusplus::exception::SdBusError(
-                            static_cast<int>(std::errc::too_many_files_open),
-                            "Reached maximal trigger count");
-                    }
+                [this](boost::asio::yield_context& yield,
+                       const std::string& name, bool isDiscrete,
+                       bool logToJournal, bool logToRedfish, bool updateReport,
+                       const SensorsInfo& sensors,
+                       const std::vector<std::string>& reportNames,
+                       const TriggerThresholdParamsExt& thresholds) {
+                    LabeledTriggerThresholdParams
+                        labeledTriggerThresholdParams = std::visit(
+                            utils::ToLabeledThresholdParamConversion(),
+                            thresholds);
 
-                    for (const auto& trigger : triggers)
-                    {
-                        if (trigger->getName() == name)
-                        {
-                            throw sdbusplus::exception::SdBusError(
-                                static_cast<int>(std::errc::file_exists),
-                                "Duplicate trigger");
-                        }
-                    }
+                    std::vector<LabeledSensorInfo> labeledSensorsInfo =
+                        triggerFactory->getLabeledSensorsInfo(yield, sensors);
 
-                    triggers.emplace_back(triggerFactory->make(
-                        yield, name, isDiscrete, logToJournal, logToRedfish,
-                        updateReport, sensors, reportNames, thresholds, *this,
-                        *triggerStorage));
-                    return triggers.back()->getPath();
+                    return addTrigger(name, isDiscrete, logToJournal,
+                                      logToRedfish, updateReport,
+                                      labeledSensorsInfo, reportNames,
+                                      labeledTriggerThresholdParams)
+                        .getPath();
                 });
         });
 }
@@ -52,3 +50,105 @@
                        [trigger](const auto& x) { return trigger == x.get(); }),
         triggers.end());
 }
+
+void TriggerManager::verifyAddTrigger(
+    const std::string& triggerName, bool isDiscrete,
+    const LabeledTriggerThresholdParams& thresholdParams)
+{
+    if (triggers.size() >= maxTriggers)
+    {
+        throw sdbusplus::exception::SdBusError(
+            static_cast<int>(std::errc::too_many_files_open),
+            "Reached maximal trigger count");
+    }
+
+    for (const auto& trigger : triggers)
+    {
+        if (trigger->getName() == triggerName)
+        {
+            throw sdbusplus::exception::SdBusError(
+                static_cast<int>(std::errc::file_exists), "Duplicate trigger");
+        }
+    }
+}
+
+interfaces::Trigger& TriggerManager::addTrigger(
+    const std::string& triggerName, bool isDiscrete, bool logToJournal,
+    bool logToRedfish, bool updateReport,
+    const std::vector<LabeledSensorInfo>& labeledSensorsInfo,
+    const std::vector<std::string>& reportNames,
+    const LabeledTriggerThresholdParams& labeledThresholdParams)
+{
+    verifyAddTrigger(triggerName, isDiscrete, labeledThresholdParams);
+
+    triggers.emplace_back(triggerFactory->make(
+        triggerName, isDiscrete, logToJournal, logToRedfish, updateReport,
+        reportNames, *this, *triggerStorage, labeledThresholdParams,
+        labeledSensorsInfo));
+
+    return *triggers.back();
+}
+
+void TriggerManager::loadFromPersistent()
+{
+    std::vector<interfaces::JsonStorage::FilePath> paths =
+        triggerStorage->list();
+
+    for (const auto& path : paths)
+    {
+        std::optional<nlohmann::json> data = triggerStorage->load(path);
+        try
+        {
+            if (!data.has_value())
+            {
+                throw std::runtime_error("Empty storage");
+            }
+            size_t version = data->at("Version").get<size_t>();
+            if (version != Trigger::triggerVersion)
+            {
+                throw std::runtime_error("Invalid version");
+            }
+            const std::string& name = data->at("Name").get_ref<std::string&>();
+            int thresholdParamsDiscriminator =
+                data->at("ThresholdParamsDiscriminator").get<int>();
+            bool isDiscrete = data->at("IsDiscrete").get<bool>();
+            bool logToJournal = data->at("LogToJournal").get<bool>();
+            bool logToRedfish = data->at("LogToRedfish").get<bool>();
+            bool updateReport = data->at("UpdateReport").get<bool>();
+
+            LabeledTriggerThresholdParams labeledThresholdParams;
+            if (0 == thresholdParamsDiscriminator)
+            {
+                labeledThresholdParams =
+                    data->at("ThresholdParams")
+                        .get<std::vector<numeric::LabeledThresholdParam>>();
+            }
+            else
+            {
+                labeledThresholdParams =
+                    data->at("ThresholdParams")
+                        .get<std::vector<discrete::LabeledThresholdParam>>();
+            }
+
+            auto reportNames =
+                data->at("ReportNames").get<std::vector<std::string>>();
+
+            auto labeledSensorsInfo =
+                data->at("Sensors").get<std::vector<LabeledSensorInfo>>();
+
+            addTrigger(name, isDiscrete, logToJournal, logToRedfish,
+                       updateReport, labeledSensorsInfo, reportNames,
+                       labeledThresholdParams);
+        }
+        catch (const std::exception& e)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "Failed to load trigger from storage",
+                phosphor::logging::entry(
+                    "FILENAME=%s",
+                    static_cast<std::filesystem::path>(path).c_str()),
+                phosphor::logging::entry("EXCEPTION_MSG=%s", e.what()));
+            triggerStorage->remove(path);
+        }
+    }
+}
diff --git a/src/trigger_manager.hpp b/src/trigger_manager.hpp
index 1db6d73..ec26bdc 100644
--- a/src/trigger_manager.hpp
+++ b/src/trigger_manager.hpp
@@ -30,6 +30,17 @@
     std::unique_ptr<sdbusplus::asio::dbus_interface> managerIface;
     std::vector<std::unique_ptr<interfaces::Trigger>> triggers;
 
+    void verifyAddTrigger(const std::string& triggerName, bool isDiscrete,
+                          const LabeledTriggerThresholdParams& thresholdParams);
+
+    interfaces::Trigger&
+        addTrigger(const std::string& triggerName, bool isDiscrete,
+                   bool logToJournal, bool logToRedfish, bool updateReport,
+                   const std::vector<LabeledSensorInfo>& labeledSensors,
+                   const std::vector<std::string>& reportNames,
+                   const LabeledTriggerThresholdParams& labeledThresholdParams);
+    void loadFromPersistent();
+
   public:
     static constexpr size_t maxTriggers{TELEMETRY_MAX_TRIGGERS};
     static constexpr const char* triggerManagerIfaceName =
diff --git a/src/utils/conversion_trigger.cpp b/src/utils/conversion_trigger.cpp
new file mode 100644
index 0000000..5aae0d5
--- /dev/null
+++ b/src/utils/conversion_trigger.cpp
@@ -0,0 +1,88 @@
+#include "utils/conversion_trigger.hpp"
+
+#include "utils/transform.hpp"
+
+#include <sdbusplus/exception.hpp>
+
+namespace utils
+{
+namespace ts = utils::tstring;
+
+LabeledTriggerThresholdParams ToLabeledThresholdParamConversion::operator()(
+    const std::monostate& arg) const
+{
+    throw sdbusplus::exception::SdBusError(
+        static_cast<int>(std::errc::invalid_argument),
+        "Provided threshold parameter is invalid");
+}
+
+LabeledTriggerThresholdParams ToLabeledThresholdParamConversion::operator()(
+    const std::vector<numeric::ThresholdParam>& arg) const
+{
+    return utils::transform(arg, [](const auto& thresholdParam) {
+        const auto& [type, dwellTime, direction, thresholdValue] =
+            thresholdParam;
+        return numeric::LabeledThresholdParam(
+            numeric::stringToType(type), dwellTime,
+            numeric::stringToDirection(direction), thresholdValue);
+    });
+}
+
+LabeledTriggerThresholdParams ToLabeledThresholdParamConversion::operator()(
+    const std::vector<discrete::ThresholdParam>& arg) const
+{
+    return utils::transform(arg, [](const auto& thresholdParam) {
+        const auto& [userId, severity, dwellTime, thresholdValue] =
+            thresholdParam;
+        return discrete::LabeledThresholdParam(
+            userId, discrete::stringToSeverity(severity), dwellTime,
+            thresholdValue);
+    });
+}
+
+TriggerThresholdParams FromLabeledThresholdParamConversion::operator()(
+    const std::vector<numeric::LabeledThresholdParam>& arg) const
+{
+    return utils::transform(
+        arg, [](const numeric::LabeledThresholdParam& labeledThresholdParam) {
+            return numeric::ThresholdParam(
+                numeric::typeToString(
+                    labeledThresholdParam.at_label<ts::Type>()),
+                labeledThresholdParam.at_label<ts::DwellTime>(),
+                numeric::directionToString(
+                    labeledThresholdParam.at_label<ts::Direction>()),
+                labeledThresholdParam.at_label<ts::ThresholdValue>());
+        });
+}
+
+TriggerThresholdParams FromLabeledThresholdParamConversion::operator()(
+    const std::vector<discrete::LabeledThresholdParam>& arg) const
+{
+    return utils::transform(
+        arg, [](const discrete::LabeledThresholdParam& labeledThresholdParam) {
+            return discrete::ThresholdParam(
+                labeledThresholdParam.at_label<ts::UserId>(),
+                discrete::severityToString(
+                    labeledThresholdParam.at_label<ts::Severity>()),
+                labeledThresholdParam.at_label<ts::DwellTime>(),
+                labeledThresholdParam.at_label<ts::ThresholdValue>());
+        });
+}
+
+SensorsInfo fromLabeledSensorsInfo(const std::vector<LabeledSensorInfo>& infos)
+{
+    return utils::transform(infos, [](const LabeledSensorInfo& val) {
+        return SensorsInfo::value_type(
+            sdbusplus::message::object_path(val.at_label<ts::SensorPath>()),
+            val.at_label<ts::SensorMetadata>());
+    });
+}
+
+nlohmann::json labeledThresholdParamsToJson(
+    const LabeledTriggerThresholdParams& labeledThresholdParams)
+{
+    return std::visit([](const auto& lt) { return nlohmann::json(lt); },
+                      labeledThresholdParams);
+}
+
+} // namespace utils
diff --git a/src/utils/conversion_trigger.hpp b/src/utils/conversion_trigger.hpp
new file mode 100644
index 0000000..7ed660f
--- /dev/null
+++ b/src/utils/conversion_trigger.hpp
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "interfaces/json_storage.hpp"
+#include "interfaces/trigger_types.hpp"
+
+namespace utils
+{
+
+class ToLabeledThresholdParamConversion
+{
+  public:
+    LabeledTriggerThresholdParams operator()(const std::monostate& arg) const;
+    LabeledTriggerThresholdParams
+        operator()(const std::vector<numeric::ThresholdParam>& arg) const;
+    LabeledTriggerThresholdParams
+        operator()(const std::vector<discrete::ThresholdParam>& arg) const;
+};
+
+class FromLabeledThresholdParamConversion
+{
+  public:
+    TriggerThresholdParams operator()(
+        const std::vector<numeric::LabeledThresholdParam>& arg) const;
+    TriggerThresholdParams operator()(
+        const std::vector<discrete::LabeledThresholdParam>& arg) const;
+};
+
+SensorsInfo fromLabeledSensorsInfo(const std::vector<LabeledSensorInfo>& infos);
+
+nlohmann::json labeledThresholdParamsToJson(
+    const LabeledTriggerThresholdParams& labeledThresholdParams);
+
+} // namespace utils
diff --git a/tests/meson.build b/tests/meson.build
index d2e5aac..e87a616 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -25,6 +25,7 @@
             '../src/trigger_actions.cpp',
             '../src/trigger_factory.cpp',
             '../src/trigger_manager.cpp',
+            '../src/utils/conversion_trigger.cpp',
             'src/dbus_environment.cpp',
             'src/main.cpp',
             'src/stubs/dbus_sensor_object.cpp',
diff --git a/tests/src/mocks/trigger_factory_mock.hpp b/tests/src/mocks/trigger_factory_mock.hpp
index 8249799..3682a46 100644
--- a/tests/src/mocks/trigger_factory_mock.hpp
+++ b/tests/src/mocks/trigger_factory_mock.hpp
@@ -3,6 +3,8 @@
 #include "interfaces/trigger_factory.hpp"
 #include "mocks/trigger_mock.hpp"
 #include "params/trigger_params.hpp"
+#include "trigger.hpp"
+#include "utils/conversion_trigger.hpp"
 
 #include <gmock/gmock.h>
 
@@ -13,26 +15,28 @@
     {
         using namespace testing;
 
-        ON_CALL(*this, make(_, _, _, _, _, _, _, _, _, _, _))
-            .WillByDefault(WithArgs<1>(Invoke([](const std::string& name) {
+        ON_CALL(*this, make(A<const std::string&>(), _, _, _, _, _, _, _, _, _))
+            .WillByDefault(WithArgs<0>(Invoke([](const std::string& name) {
                 return std::make_unique<NiceMock<TriggerMock>>(name);
             })));
     }
 
-    MOCK_METHOD(
-        std::unique_ptr<interfaces::Trigger>, make,
-        (boost::asio::yield_context&, const std::string& name, bool isDiscrete,
-         bool logToJournal, bool logToRedfish, bool updateReport,
-         (const std::vector<
-             std::pair<sdbusplus::message::object_path, std::string>>& sensors),
-         const std::vector<std::string>& reportNames,
-         const TriggerThresholdParams& thresholdParams,
-         interfaces::TriggerManager& triggerManager,
-         interfaces::JsonStorage& triggerStorage),
-        (const, override));
+    MOCK_METHOD(std::unique_ptr<interfaces::Trigger>, make,
+                (const std::string& name, bool isDiscrete, bool logToJournal,
+                 bool logToRedfish, bool updateReport,
+                 const std::vector<std::string>& reportNames,
+                 interfaces::TriggerManager& triggerManager,
+                 interfaces::JsonStorage& triggerStorage,
+                 const LabeledTriggerThresholdParams& labeledThresholdParams,
+                 const std::vector<LabeledSensorInfo>& labeledSensorsInfo),
+                (const, override));
+
+    MOCK_METHOD(std::vector<LabeledSensorInfo>, getLabeledSensorsInfo,
+                (boost::asio::yield_context&, const SensorsInfo&),
+                (const, override));
 
     auto& expectMake(
-        std::optional<std::reference_wrapper<const TriggerParams>> paramsOpt,
+        std::optional<TriggerParams> paramsOpt,
         const testing::Matcher<interfaces::TriggerManager&>& tm,
         const testing::Matcher<interfaces::JsonStorage&>& triggerStorage)
     {
@@ -41,17 +45,32 @@
         if (paramsOpt)
         {
             const TriggerParams& params = *paramsOpt;
+
+            const auto sensorInfos =
+                utils::fromLabeledSensorsInfo(params.sensors());
+
+            ON_CALL(*this, getLabeledSensorsInfo(_, sensorInfos))
+                .WillByDefault(Return(params.sensors()));
+
             return EXPECT_CALL(
-                *this, make(_, params.name(), params.isDiscrete(),
-                            params.logToJournal(), params.logToRedfish(),
-                            params.updateReport(), params.sensors(),
-                            params.reportNames(), params.thresholdParams(), tm,
-                            triggerStorage));
+                *this,
+                make(params.name(), params.isDiscrete(), params.logToJournal(),
+                     params.logToRedfish(), params.updateReport(),
+                     params.reportNames(), tm, triggerStorage,
+                     params.thresholdParams(), params.sensors()));
         }
         else
         {
+            const std::vector<LabeledSensorInfo> dummy = {
+                {"service99",
+                 "/xyz/openbmc_project/sensors/temperature/BMC_Temp99",
+                 "metadata99"}};
+
+            ON_CALL(*this, getLabeledSensorsInfo(_, _))
+                .WillByDefault(Return(dummy));
+
             return EXPECT_CALL(
-                *this, make(_, _, _, _, _, _, _, _, _, tm, triggerStorage));
+                *this, make(_, _, _, _, _, _, tm, triggerStorage, _, dummy));
         }
     }
 };
diff --git a/tests/src/params/trigger_params.hpp b/tests/src/params/trigger_params.hpp
index 08bed4b..7e00157 100644
--- a/tests/src/params/trigger_params.hpp
+++ b/tests/src/params/trigger_params.hpp
@@ -53,9 +53,9 @@
         return updateReportProperty;
     }
 
-    const TriggerSensors& sensors() const
+    const std::vector<LabeledSensorInfo>& sensors() const
     {
-        return sensorsProperty;
+        return labeledSensorsProperty;
     }
 
     const std::vector<std::string>& reportNames() const
@@ -63,15 +63,15 @@
         return reportNamesProperty;
     }
 
-    TriggerParams& thresholdParams(TriggerThresholdParams val)
+    TriggerParams& thresholdParams(LabeledTriggerThresholdParams val)
     {
-        thresholdsProperty = std::move(val);
+        labeledThresholdsProperty = std::move(val);
         return *this;
     }
 
-    const TriggerThresholdParams& thresholdParams() const
+    const LabeledTriggerThresholdParams& thresholdParams() const
     {
-        return thresholdsProperty;
+        return labeledThresholdsProperty;
     }
 
   private:
@@ -80,17 +80,20 @@
     bool logToJournalProperty = false;
     bool logToRedfishProperty = false;
     bool updateReportProperty = true;
-    TriggerSensors sensorsProperty = {
-        {sdbusplus::message::object_path(
-             "/xyz/openbmc_project/sensors/temperature/BMC_Temp"),
-         ""}};
+    std::vector<LabeledSensorInfo> labeledSensorsProperty = {
+        {"service1", "/xyz/openbmc_project/sensors/temperature/BMC_Temp",
+         "metadata1"}};
+
     std::vector<std::string> reportNamesProperty = {"Report1"};
-    TriggerThresholdParams thresholdsProperty =
-        std::vector<numeric::ThresholdParam>{
-            {numeric::typeToString(numeric::Type::lowerCritical),
-             std::chrono::milliseconds(10).count(),
-             numeric::directionToString(numeric::Direction::decreasing), 0.0},
-            {numeric::typeToString(numeric::Type::upperCritical),
-             std::chrono::milliseconds(10).count(),
-             numeric::directionToString(numeric::Direction::increasing), 90.0}};
+
+    LabeledTriggerThresholdParams labeledThresholdsProperty =
+        std::vector<numeric::LabeledThresholdParam>{
+            numeric::LabeledThresholdParam{
+                numeric::Type::lowerCritical,
+                std::chrono::milliseconds(10).count(),
+                numeric::Direction::decreasing, 0.5},
+            numeric::LabeledThresholdParam{
+                numeric::Type::upperCritical,
+                std::chrono::milliseconds(10).count(),
+                numeric::Direction::increasing, 90.2}};
 };
diff --git a/tests/src/test_trigger.cpp b/tests/src/test_trigger.cpp
index f396589..cf7d96c 100644
--- a/tests/src/test_trigger.cpp
+++ b/tests/src/test_trigger.cpp
@@ -4,7 +4,12 @@
 #include "mocks/trigger_manager_mock.hpp"
 #include "params/trigger_params.hpp"
 #include "trigger.hpp"
+#include "utils/conversion_trigger.hpp"
 #include "utils/set_exception.hpp"
+#include "utils/transform.hpp"
+#include "utils/tstring.hpp"
+
+#include <boost/range/combine.hpp>
 
 using namespace testing;
 using namespace std::literals::string_literals;
@@ -15,6 +20,18 @@
 {
   public:
     TriggerParams triggerParams;
+    TriggerParams triggerDiscreteParams =
+        TriggerParams()
+            .name("Trigger2")
+            .isDiscrete(true)
+            .thresholdParams(std::vector<discrete::LabeledThresholdParam>{
+                discrete::LabeledThresholdParam{
+                    "userId", discrete::Severity::warning,
+                    std::chrono::milliseconds(10).count(), 15.2},
+                discrete::LabeledThresholdParam{
+                    "userId_2", discrete::Severity::critical,
+                    std::chrono::milliseconds(5).count(), 32.7},
+            });
 
     std::unique_ptr<TriggerManagerMock> triggerManagerMockPtr =
         std::make_unique<NiceMock<TriggerManagerMock>>();
@@ -26,13 +43,22 @@
         sut = makeTrigger(triggerParams);
     }
 
+    static std::vector<LabeledSensorInfo>
+        convertToLabeledSensor(const SensorsInfo& sensorsInfo)
+    {
+        return utils::transform(sensorsInfo, [](const auto& sensorInfo) {
+            const auto& [sensorPath, sensorMetadata] = sensorInfo;
+            return LabeledSensorInfo("service1", sensorPath, sensorMetadata);
+        });
+    }
+
     std::unique_ptr<Trigger> makeTrigger(const TriggerParams& params)
     {
         return std::make_unique<Trigger>(
             DbusEnvironment::getIoc(), DbusEnvironment::getObjServer(),
             params.name(), params.isDiscrete(), params.logToJournal(),
-            params.logToRedfish(), params.updateReport(), params.sensors(),
-            params.reportNames(), params.thresholdParams(),
+            params.logToRedfish(), params.updateReport(), params.reportNames(),
+            params.sensors(), params.thresholdParams(),
             std::vector<std::shared_ptr<interfaces::Threshold>>{},
             *triggerManagerMockPtr, storageMock);
     }
@@ -104,16 +130,67 @@
                 Eq(triggerParams.logToRedfish()));
     EXPECT_THAT(getProperty<bool>(sut->getPath(), "UpdateReport"),
                 Eq(triggerParams.updateReport()));
-    EXPECT_THAT((getProperty<std::vector<
-                     std::pair<sdbusplus::message::object_path, std::string>>>(
-                    sut->getPath(), "Sensors")),
-                Eq(triggerParams.sensors()));
+    EXPECT_THAT((getProperty<SensorsInfo>(sut->getPath(), "Sensors")),
+                Eq(utils::fromLabeledSensorsInfo(triggerParams.sensors())));
     EXPECT_THAT(
         getProperty<std::vector<std::string>>(sut->getPath(), "ReportNames"),
         Eq(triggerParams.reportNames()));
     EXPECT_THAT(
         getProperty<TriggerThresholdParams>(sut->getPath(), "Thresholds"),
-        Eq(triggerParams.thresholdParams()));
+        Eq(std::visit(utils::FromLabeledThresholdParamConversion(),
+                      triggerParams.thresholdParams())));
+}
+
+TEST_F(TestTrigger, checkIfNumericCoversionsAreGood)
+{
+    const auto& labeledParamsBase =
+        std::get<std::vector<numeric::LabeledThresholdParam>>(
+            triggerParams.thresholdParams());
+    const auto paramsToCheck =
+        std::visit(utils::FromLabeledThresholdParamConversion(),
+                   triggerParams.thresholdParams());
+    const auto labeledParamsToCheck =
+        std::get<std::vector<numeric::LabeledThresholdParam>>(std::visit(
+            utils::ToLabeledThresholdParamConversion(), paramsToCheck));
+
+    for (const auto& [tocheck, base] :
+         boost::combine(labeledParamsToCheck, labeledParamsBase))
+    {
+        EXPECT_THAT(tocheck.at_label<utils::tstring::Type>(),
+                    Eq(base.at_label<utils::tstring::Type>()));
+        EXPECT_THAT(tocheck.at_label<utils::tstring::Direction>(),
+                    Eq(base.at_label<utils::tstring::Direction>()));
+        EXPECT_THAT(tocheck.at_label<utils::tstring::DwellTime>(),
+                    Eq(base.at_label<utils::tstring::DwellTime>()));
+        EXPECT_THAT(tocheck.at_label<utils::tstring::ThresholdValue>(),
+                    Eq(base.at_label<utils::tstring::ThresholdValue>()));
+    }
+}
+
+TEST_F(TestTrigger, checkIfDiscreteCoversionsAreGood)
+{
+    const auto& labeledParamsBase =
+        std::get<std::vector<discrete::LabeledThresholdParam>>(
+            triggerDiscreteParams.thresholdParams());
+    const auto paramsToCheck =
+        std::visit(utils::FromLabeledThresholdParamConversion(),
+                   triggerDiscreteParams.thresholdParams());
+    const auto labeledParamsToCheck =
+        std::get<std::vector<discrete::LabeledThresholdParam>>(std::visit(
+            utils::ToLabeledThresholdParamConversion(), paramsToCheck));
+
+    for (const auto& [tocheck, base] :
+         boost::combine(labeledParamsToCheck, labeledParamsBase))
+    {
+        EXPECT_THAT(tocheck.at_label<utils::tstring::UserId>(),
+                    Eq(base.at_label<utils::tstring::UserId>()));
+        EXPECT_THAT(tocheck.at_label<utils::tstring::Severity>(),
+                    Eq(base.at_label<utils::tstring::Severity>()));
+        EXPECT_THAT(tocheck.at_label<utils::tstring::DwellTime>(),
+                    Eq(base.at_label<utils::tstring::DwellTime>()));
+        EXPECT_THAT(tocheck.at_label<utils::tstring::ThresholdValue>(),
+                    Eq(base.at_label<utils::tstring::ThresholdValue>()));
+    }
 }
 
 TEST_F(TestTrigger, deleteTrigger)
@@ -130,12 +207,12 @@
     EXPECT_THAT(ec.value(), Eq(EBADR));
 }
 
-TEST_F(TestTrigger, settingPersistencyToFalseRemovesReportFromStorage)
+TEST_F(TestTrigger, settingPersistencyToFalseRemovesTriggerFromStorage)
 {
     EXPECT_CALL(storageMock, remove(to_file_path(sut->getName())));
 
     bool persistent = false;
-    EXPECT_THAT(setProperty(sut->getPath(), "Persistent", persistent).value(),
+    EXPECT_THAT(setProperty(sut->getPath(), "Persistent", persistent),
                 Eq(boost::system::errc::success));
     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistent"),
                 Eq(persistent));
@@ -150,7 +227,7 @@
     nlohmann::json storedConfiguration;
 };
 
-TEST_F(TestTriggerErrors, throwingExceptionDoesNotStoreTriggerReportNames)
+TEST_F(TestTriggerErrors, exceptionDuringTriggerStoreDisablesPersistency)
 {
     EXPECT_CALL(storageMock, store(_, _))
         .WillOnce(Throw(std::runtime_error("Generic error!")));
@@ -171,15 +248,20 @@
 class TestTriggerStore : public TestTrigger
 {
   public:
+    nlohmann::json storedConfiguration;
+    nlohmann::json storedDiscreteConfiguration;
+    std::unique_ptr<Trigger> sutDiscrete;
+
     void SetUp() override
     {
         ON_CALL(storageMock, store(_, _))
             .WillByDefault(SaveArg<1>(&storedConfiguration));
-
         sut = makeTrigger(triggerParams);
-    }
 
-    nlohmann::json storedConfiguration;
+        ON_CALL(storageMock, store(_, _))
+            .WillByDefault(SaveArg<1>(&storedDiscreteConfiguration));
+        sutDiscrete = makeTrigger(triggerDiscreteParams);
+    }
 };
 
 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerVersion)
@@ -225,29 +307,50 @@
 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerSensors)
 {
     nlohmann::json expectedItem;
+    expectedItem["service"] = "service1";
     expectedItem["sensorPath"] =
         "/xyz/openbmc_project/sensors/temperature/BMC_Temp";
-    expectedItem["sensorMetadata"] = "";
+    expectedItem["sensorMetadata"] = "metadata1";
 
     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;
+    expectedItem0["thresholdValue"] = 0.5;
 
     nlohmann::json expectedItem1;
     expectedItem1["type"] = 3;
     expectedItem1["dwellTime"] = 10;
     expectedItem1["direction"] = 2;
-    expectedItem1["thresholdValue"] = 90.0;
+    expectedItem1["thresholdValue"] = 90.2;
 
+    ASSERT_THAT(storedConfiguration.at("ThresholdParamsDiscriminator"), Eq(0));
     ASSERT_THAT(storedConfiguration.at("ThresholdParams"),
                 ElementsAre(expectedItem0, expectedItem1));
 }
+
+TEST_F(TestTriggerStore,
+       settingPersistencyToTrueStoresDiscreteTriggerThresholdParams)
+{
+    nlohmann::json expectedItem0;
+    expectedItem0["userId"] = "userId";
+    expectedItem0["severity"] = discrete::Severity::warning;
+    expectedItem0["dwellTime"] = 10;
+    expectedItem0["thresholdValue"] = 15.2;
+
+    nlohmann::json expectedItem1;
+    expectedItem1["userId"] = "userId_2";
+    expectedItem1["severity"] = discrete::Severity::critical;
+    expectedItem1["dwellTime"] = 5;
+    expectedItem1["thresholdValue"] = 32.7;
+
+    ASSERT_THAT(storedDiscreteConfiguration.at("ThresholdParamsDiscriminator"),
+                Eq(1));
+    ASSERT_THAT(storedDiscreteConfiguration.at("ThresholdParams"),
+                ElementsAre(expectedItem0, expectedItem1));
+}
diff --git a/tests/src/test_trigger_manager.cpp b/tests/src/test_trigger_manager.cpp
index 1bc4f1b..773b809 100644
--- a/tests/src/test_trigger_manager.cpp
+++ b/tests/src/test_trigger_manager.cpp
@@ -4,7 +4,10 @@
 #include "mocks/trigger_factory_mock.hpp"
 #include "mocks/trigger_mock.hpp"
 #include "params/trigger_params.hpp"
+#include "trigger.hpp"
 #include "trigger_manager.hpp"
+#include "utils/conversion_trigger.hpp"
+#include "utils/transform.hpp"
 
 using namespace testing;
 
@@ -14,6 +17,9 @@
     std::pair<boost::system::error_code, std::string>
         addTrigger(const TriggerParams& params)
     {
+        const auto sensorInfos =
+            utils::fromLabeledSensorsInfo(params.sensors());
+
         std::promise<std::pair<boost::system::error_code, std::string>>
             addTriggerPromise;
         DbusEnvironment::getBus()->async_method_call(
@@ -24,12 +30,25 @@
             DbusEnvironment::serviceName(), TriggerManager::triggerManagerPath,
             TriggerManager::triggerManagerIfaceName, "AddTrigger",
             params.name(), params.isDiscrete(), params.logToJournal(),
-            params.logToRedfish(), params.updateReport(), params.sensors(),
-            params.reportNames(), params.thresholdParams());
+            params.logToRedfish(), params.updateReport(), sensorInfos,
+            params.reportNames(),
+            std::visit(utils::FromLabeledThresholdParamConversion(),
+                       params.thresholdParams()));
         return DbusEnvironment::waitForFuture(addTriggerPromise.get_future());
     }
 
-    TriggerParams triggerParams;
+    std::unique_ptr<TriggerManager> makeTriggerManager()
+    {
+        return std::make_unique<TriggerManager>(
+            std::move(triggerFactoryMockPtr), std::move(storageMockPtr),
+            DbusEnvironment::getObjServer());
+    }
+
+    void SetUp() override
+    {
+        sut = makeTriggerManager();
+    }
+
     std::unique_ptr<StorageMock> storageMockPtr =
         std::make_unique<NiceMock<StorageMock>>();
     StorageMock& storageMock = *storageMockPtr;
@@ -37,20 +56,18 @@
         std::make_unique<NiceMock<TriggerFactoryMock>>();
     TriggerFactoryMock& triggerFactoryMock = *triggerFactoryMockPtr;
     std::unique_ptr<TriggerMock> triggerMockPtr =
-        std::make_unique<NiceMock<TriggerMock>>(triggerParams.name());
+        std::make_unique<NiceMock<TriggerMock>>(TriggerParams().name());
     TriggerMock& triggerMock = *triggerMockPtr;
-    std::unique_ptr<TriggerManager> sut = std::make_unique<TriggerManager>(
-        std::move(triggerFactoryMockPtr), std::move(storageMockPtr),
-        std::move(DbusEnvironment::getObjServer()));
+    std::unique_ptr<TriggerManager> sut;
     MockFunction<void(std::string)> checkPoint;
 };
 
 TEST_F(TestTriggerManager, addTrigger)
 {
-    triggerFactoryMock.expectMake(triggerParams, Ref(*sut), Ref(storageMock))
+    triggerFactoryMock.expectMake(TriggerParams(), Ref(*sut), Ref(storageMock))
         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
 
-    auto [ec, path] = addTrigger(triggerParams);
+    auto [ec, path] = addTrigger(TriggerParams());
     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
     EXPECT_THAT(path, Eq(triggerMock.getPath()));
 }
@@ -58,13 +75,10 @@
 TEST_F(TestTriggerManager, addTriggerWithDiscreteThresholds)
 {
     TriggerParams triggerParamsDiscrete;
-    auto thresholds = std::vector<discrete::ThresholdParam>{
-        {"discrete_threshold1",
-         discrete::severityToString(discrete::Severity::ok), 10, 11.0},
-        {"discrete_threshold2",
-         discrete::severityToString(discrete::Severity::warning), 10, 12.0},
-        {"discrete_threshold3",
-         discrete::severityToString(discrete::Severity::critical), 10, 13.0}};
+    auto thresholds = std::vector<discrete::LabeledThresholdParam>{
+        {"discrete_threshold1", discrete::Severity::ok, 10, 11.0},
+        {"discrete_threshold2", discrete::Severity::warning, 10, 12.0},
+        {"discrete_threshold3", discrete::Severity::critical, 10, 13.0}};
 
     triggerParamsDiscrete.thresholdParams(thresholds).isDiscrete(true);
 
@@ -76,7 +90,7 @@
 TEST_F(TestTriggerManager, addDiscreteTriggerWithoutThresholds)
 {
     TriggerParams triggerParamsDiscrete;
-    auto thresholds = std::vector<discrete::ThresholdParam>();
+    auto thresholds = std::vector<discrete::LabeledThresholdParam>();
 
     triggerParamsDiscrete.thresholdParams(thresholds).isDiscrete(true);
 
@@ -87,30 +101,32 @@
 
 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerTwice)
 {
-    triggerFactoryMock.expectMake(triggerParams, Ref(*sut), Ref(storageMock))
+    triggerFactoryMock.expectMake(TriggerParams(), Ref(*sut), Ref(storageMock))
         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
 
-    addTrigger(triggerParams);
+    addTrigger(TriggerParams());
 
-    auto [ec, path] = addTrigger(triggerParams);
+    auto [ec, path] = addTrigger(TriggerParams());
     EXPECT_THAT(ec.value(), Eq(boost::system::errc::file_exists));
     EXPECT_THAT(path, Eq(std::string()));
 }
 
 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWhenMaxTriggerIsReached)
 {
+    auto triggerParams = TriggerParams();
+
     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
         .Times(TriggerManager::maxTriggers);
 
     for (size_t i = 0; i < TriggerManager::maxTriggers; i++)
     {
-        triggerParams.name(triggerParams.name() + std::to_string(i));
+        triggerParams.name(TriggerParams().name() + std::to_string(i));
 
         auto [ec, path] = addTrigger(triggerParams);
         EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
     }
 
-    triggerParams.name(triggerParams.name() +
+    triggerParams.name(TriggerParams().name() +
                        std::to_string(TriggerManager::maxTriggers));
     auto [ec, path] = addTrigger(triggerParams);
     EXPECT_THAT(ec.value(), Eq(boost::system::errc::too_many_files_open));
@@ -122,13 +138,13 @@
     {
         InSequence seq;
         triggerFactoryMock
-            .expectMake(triggerParams, Ref(*sut), Ref(storageMock))
+            .expectMake(TriggerParams(), Ref(*sut), Ref(storageMock))
             .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
         EXPECT_CALL(triggerMock, Die());
         EXPECT_CALL(checkPoint, Call("end"));
     }
 
-    addTrigger(triggerParams);
+    addTrigger(TriggerParams());
     sut->removeTrigger(&triggerMock);
     checkPoint.Call("end");
 }
@@ -150,14 +166,101 @@
     {
         InSequence seq;
         triggerFactoryMock
-            .expectMake(triggerParams, Ref(*sut), Ref(storageMock))
+            .expectMake(TriggerParams(), Ref(*sut), Ref(storageMock))
             .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
         EXPECT_CALL(triggerMock, Die());
         EXPECT_CALL(checkPoint, Call("end"));
     }
 
-    addTrigger(triggerParams);
+    addTrigger(TriggerParams());
     sut->removeTrigger(&triggerMock);
     sut->removeTrigger(&triggerMock);
     checkPoint.Call("end");
 }
+
+class TestTriggerManagerStorage : public TestTriggerManager
+{
+  public:
+    using FilePath = interfaces::JsonStorage::FilePath;
+    using DirectoryPath = interfaces::JsonStorage::DirectoryPath;
+
+    void SetUp() override
+    {
+        ON_CALL(storageMock, list())
+            .WillByDefault(Return(std::vector<FilePath>{
+                {FilePath("trigger1")}, {FilePath("trigger2")}}));
+
+        ON_CALL(storageMock, load(FilePath("trigger1")))
+            .WillByDefault(InvokeWithoutArgs([this] { return data1; }));
+
+        data2["Name"] = "Trigger2";
+        ON_CALL(storageMock, load(FilePath("trigger2")))
+            .WillByDefault(InvokeWithoutArgs([this] { return data2; }));
+    }
+
+    nlohmann::json data1 = nlohmann::json{
+        {"Version", Trigger::triggerVersion},
+        {"Name", TriggerParams().name()},
+        {"ThresholdParamsDiscriminator",
+         TriggerParams().thresholdParams().index()},
+        {"IsDiscrete", TriggerParams().isDiscrete()},
+        {"LogToJournal", TriggerParams().logToJournal()},
+        {"LogToRedfish", TriggerParams().logToRedfish()},
+        {"UpdateReport", TriggerParams().updateReport()},
+        {"ThresholdParams", utils::labeledThresholdParamsToJson(
+                                TriggerParams().thresholdParams())},
+        {"ReportNames", TriggerParams().reportNames()},
+        {"Sensors", TriggerParams().sensors()}};
+
+    nlohmann::json data2 = data1;
+};
+
+TEST_F(TestTriggerManagerStorage, triggerManagerCtorAddTriggerFromStorage)
+{
+    triggerFactoryMock.expectMake(TriggerParams(), _, Ref(storageMock));
+    triggerFactoryMock.expectMake(TriggerParams().name("Trigger2"), _,
+                                  Ref(storageMock));
+    EXPECT_CALL(storageMock, remove(_)).Times(0);
+
+    sut = makeTriggerManager();
+}
+
+TEST_F(TestTriggerManagerStorage,
+       triggerManagerCtorRemoveDiscreteTriggerFromStorage)
+{
+    LabeledTriggerThresholdParams thresholdParams =
+        std::vector<discrete::LabeledThresholdParam>{
+            {"userId1", discrete::Severity::warning, 15, 10.0},
+            {"userId2", discrete::Severity::critical, 5, 20.0}};
+
+    data1["ThresholdParamsDiscriminator"] = thresholdParams.index();
+
+    data1["ThresholdParams"] =
+        utils::labeledThresholdParamsToJson(thresholdParams);
+
+    EXPECT_CALL(storageMock, remove(FilePath("trigger1"))).Times(0);
+
+    sut = makeTriggerManager();
+}
+
+TEST_F(TestTriggerManagerStorage,
+       triggerManagerCtorRemoveDiscreteTriggerFromStorage2)
+{
+    data1["IsDiscrete"] = true;
+
+    EXPECT_CALL(storageMock, remove(FilePath("trigger1"))).Times(0);
+
+    sut = makeTriggerManager();
+}
+
+TEST_F(TestTriggerManagerStorage,
+       triggerManagerCtorAddProperRemoveInvalidTriggerFromStorage)
+{
+    data1["Version"] = Trigger::triggerVersion - 1;
+
+    triggerFactoryMock.expectMake(TriggerParams().name("Trigger2"), _,
+                                  Ref(storageMock));
+    EXPECT_CALL(storageMock, remove(FilePath("trigger1")));
+
+    sut = makeTriggerManager();
+}