Trigger: make dbus properties writable

This change allows to modify 'Sensors', 'ReportNames' and 'Thresholds'
dbus properties of Trigger interface. They are required by Redfish to
implement PATCH functionality for Trigger schema.

Some backend changes were required to enable this functionality, and as
such few improvements were made for existing code:
- NumericThreshold and DiscreteThreshold now have common implementation
  where it was possible.
- Internal sensor info structure for Trigger is now the same as the one
  used for Report. This resulted in breaking compatibility with previous
  Trigger persistency data.
- Added getInfo / getParams methods for Sensor and Threshold interfaces.
  They are used by Trigger dbus getters and persistency mechanism now,
  instead of storing this data in Trigger object.

Testing done:
- Unit tests were expanded and are passing
- dbus setters for Sensors and Thresholds are working and modifications
  are reflected by calling appropriate getters.

Signed-off-by: Szymon Dompke <szymon.dompke@intel.com>
Change-Id: I7a14c15a30d78ce872342b5f938aba43c77be9c0
diff --git a/src/utils/conversion_trigger.cpp b/src/utils/conversion_trigger.cpp
index ab06a48..e1b835a 100644
--- a/src/utils/conversion_trigger.cpp
+++ b/src/utils/conversion_trigger.cpp
@@ -1,6 +1,7 @@
 #include "utils/conversion_trigger.hpp"
 
 #include "utils/transform.hpp"
+#include "utils/tstring.hpp"
 
 #include <sdbusplus/exception.hpp>
 
@@ -72,11 +73,61 @@
 {
     return utils::transform(infos, [](const LabeledSensorInfo& val) {
         return SensorsInfo::value_type(
-            sdbusplus::message::object_path(val.at_label<ts::SensorPath>()),
+            sdbusplus::message::object_path(val.at_label<ts::Path>()),
             val.at_label<ts::Metadata>());
     });
 }
 
+TriggerThresholdParams
+    fromLabeledThresholdParam(const std::vector<LabeledThresholdParam>& params)
+{
+    namespace ts = utils::tstring;
+    if (isFirstElementOfType<std::monostate>(params))
+    {
+        return std::vector<discrete::ThresholdParam>();
+    }
+
+    if (isFirstElementOfType<discrete::LabeledThresholdParam>(params))
+    {
+        return utils::transform(params, [](const auto& param) {
+            const discrete::LabeledThresholdParam* paramUnpacked =
+                std::get_if<discrete::LabeledThresholdParam>(&param);
+            if (!paramUnpacked)
+            {
+                throw std::runtime_error(
+                    "Mixing threshold types is not allowed");
+            }
+            return discrete::ThresholdParam(
+                paramUnpacked->at_label<ts::UserId>(),
+                discrete::severityToString(
+                    paramUnpacked->at_label<ts::Severity>()),
+                paramUnpacked->at_label<ts::DwellTime>(),
+                paramUnpacked->at_label<ts::ThresholdValue>());
+        });
+    }
+
+    if (isFirstElementOfType<numeric::LabeledThresholdParam>(params))
+    {
+        return utils::transform(params, [](const auto& param) {
+            const numeric::LabeledThresholdParam* paramUnpacked =
+                std::get_if<numeric::LabeledThresholdParam>(&param);
+            if (!paramUnpacked)
+            {
+                throw std::runtime_error(
+                    "Mixing threshold types is not allowed");
+            }
+            return numeric::ThresholdParam(
+                numeric::typeToString(paramUnpacked->at_label<ts::Type>()),
+                paramUnpacked->at_label<ts::DwellTime>(),
+                numeric::directionToString(
+                    paramUnpacked->at_label<ts::Direction>()),
+                paramUnpacked->at_label<ts::ThresholdValue>());
+        });
+    }
+
+    throw std::runtime_error("Incorrect threshold params");
+}
+
 nlohmann::json labeledThresholdParamsToJson(
     const LabeledTriggerThresholdParams& labeledThresholdParams)
 {
diff --git a/src/utils/conversion_trigger.hpp b/src/utils/conversion_trigger.hpp
index 5229e5e..a749962 100644
--- a/src/utils/conversion_trigger.hpp
+++ b/src/utils/conversion_trigger.hpp
@@ -27,7 +27,32 @@
 
 SensorsInfo fromLabeledSensorsInfo(const std::vector<LabeledSensorInfo>& infos);
 
+TriggerThresholdParams
+    fromLabeledThresholdParam(const std::vector<LabeledThresholdParam>& params);
+
 nlohmann::json labeledThresholdParamsToJson(
     const LabeledTriggerThresholdParams& labeledThresholdParams);
 
+template <typename T>
+struct is_variant : std::false_type
+{};
+
+template <typename... Args>
+struct is_variant<std::variant<Args...>> : std::true_type
+{};
+
+template <typename T>
+inline constexpr bool is_variant_v = is_variant<T>::value;
+
+template <typename AlternativeT, typename VariantT>
+requires is_variant_v<VariantT>
+bool isFirstElementOfType(const std::vector<VariantT>& collection)
+{
+    if (collection.empty())
+    {
+        return false;
+    }
+    return std::holds_alternative<AlternativeT>(*collection.begin());
+}
+
 } // namespace utils
diff --git a/src/utils/dbus_mapper.hpp b/src/utils/dbus_mapper.hpp
index 78cf7b2..5f07445 100644
--- a/src/utils/dbus_mapper.hpp
+++ b/src/utils/dbus_mapper.hpp
@@ -17,19 +17,20 @@
 using SensorIfaces = std::vector<std::pair<ServiceName, Ifaces>>;
 using SensorTree = std::pair<SensorPath, SensorIfaces>;
 
+constexpr std::array<const char*, 1> sensorInterfaces = {
+    "xyz.openbmc_project.Sensor.Value"};
+
 inline std::vector<SensorTree>
     getSubTreeSensors(boost::asio::yield_context& yield,
                       const std::shared_ptr<sdbusplus::asio::connection>& bus)
 {
-    std::array<const char*, 1> interfaces = {
-        "xyz.openbmc_project.Sensor.Value"};
     boost::system::error_code ec;
 
     auto tree = bus->yield_method_call<std::vector<SensorTree>>(
         yield, ec, "xyz.openbmc_project.ObjectMapper",
         "/xyz/openbmc_project/object_mapper",
         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
-        "/xyz/openbmc_project/sensors", 2, interfaces);
+        "/xyz/openbmc_project/sensors", 2, sensorInterfaces);
     if (ec)
     {
         throw std::runtime_error("Failed to query ObjectMapper!");
@@ -37,4 +38,20 @@
     return tree;
 }
 
+inline std::vector<SensorTree>
+    getSubTreeSensors(const std::shared_ptr<sdbusplus::asio::connection>& bus)
+{
+    auto method_call =
+        bus->new_method_call("xyz.openbmc_project.ObjectMapper",
+                             "/xyz/openbmc_project/object_mapper",
+                             "xyz.openbmc_project.ObjectMapper", "GetSubTree");
+    method_call.append("/xyz/openbmc_project/sensors/", 2, sensorInterfaces);
+    auto reply = bus->call(method_call);
+
+    std::vector<SensorTree> tree;
+    reply.read(tree);
+
+    return tree;
+}
+
 } // namespace utils
diff --git a/src/utils/threshold_operations.hpp b/src/utils/threshold_operations.hpp
new file mode 100644
index 0000000..04b0195
--- /dev/null
+++ b/src/utils/threshold_operations.hpp
@@ -0,0 +1,69 @@
+#pragma once
+
+#include "interfaces/sensor.hpp"
+
+#include <boost/asio/io_context.hpp>
+
+struct ThresholdOperations
+{
+    template <typename ThresholdType>
+    void initialize(ThresholdType* thresholdPtr)
+    {
+        for ([[maybe_unused]] auto& [sensor, detail] :
+             thresholdPtr->sensorDetails)
+        {
+            sensor->registerForUpdates(thresholdPtr->weak_from_this());
+        }
+        thresholdPtr->initialized = true;
+    }
+
+    template <typename ThresholdType>
+    typename ThresholdType::ThresholdDetail&
+        getDetails(ThresholdType* thresholdPtr,
+                   const interfaces::Sensor& sensor)
+    {
+        auto it = std::find_if(
+            thresholdPtr->sensorDetails.begin(),
+            thresholdPtr->sensorDetails.end(),
+            [&sensor](const auto& x) { return &sensor == x.first.get(); });
+        return *it->second;
+    }
+
+    template <typename ThresholdType>
+    void updateSensors(ThresholdType* thresholdPtr, Sensors newSensors)
+    {
+        typename ThresholdType::SensorDetails newSensorDetails;
+        typename ThresholdType::SensorDetails oldSensorDetails =
+            thresholdPtr->sensorDetails;
+
+        for (const auto& sensor : newSensors)
+        {
+            auto it = std::find_if(
+                oldSensorDetails.begin(), oldSensorDetails.end(),
+                [&sensor](const auto& sd) { return sensor == sd.first; });
+            if (it != oldSensorDetails.end())
+            {
+                newSensorDetails.emplace(*it);
+                oldSensorDetails.erase(it);
+                continue;
+            }
+
+            newSensorDetails.emplace(
+                sensor, thresholdPtr->makeDetails(sensor->getName()));
+            if (thresholdPtr->initialized)
+            {
+                sensor->registerForUpdates(thresholdPtr->weak_from_this());
+            }
+        }
+
+        if (thresholdPtr->initialized)
+        {
+            for ([[maybe_unused]] auto& [sensor, detail] : oldSensorDetails)
+            {
+                sensor->unregisterFromUpdates(thresholdPtr->weak_from_this());
+            }
+        }
+
+        thresholdPtr->sensorDetails = std::move(newSensorDetails);
+    }
+};