Add Id to Trigger

Currently, Trigger is using Name as unique identifier. By adding Id, we
can be compliant with redfish specification:
- Id will be used as unique identifier
- Name will be used as human readable, non-unique name

AddTrigger dbus method is now requiring both id and name. Each of them
can be passed as empty string and the service will fill them with
correct values. If only id is an empty string, name will be used to
generate its value.

Dbus object path and persistent storage filename are now be based on id,
instead of name.

Added validation for AddTrigger:
- correct characters in id
- max id length

Added Name property for Trigger object, which can be modified from dbus.

Testing done:
- Unit test added and passing
- Trigger was added using dbus, without errors
- Id generation is working properly
- Name property is accessible and writable from dbus

Signed-off-by: Szymon Dompke <szymon.dompke@intel.com>
Change-Id: Ibb553586eaf51943044c93a35bc0725e6ef67ce9
diff --git a/src/interfaces/trigger.hpp b/src/interfaces/trigger.hpp
index 328e581..4ec5070 100644
--- a/src/interfaces/trigger.hpp
+++ b/src/interfaces/trigger.hpp
@@ -10,7 +10,7 @@
   public:
     virtual ~Trigger() = default;
 
-    virtual std::string getName() const = 0;
+    virtual std::string getId() const = 0;
     virtual std::string getPath() const = 0;
 };
 
diff --git a/src/interfaces/trigger_factory.hpp b/src/interfaces/trigger_factory.hpp
index 7a39aeb..f39d555 100644
--- a/src/interfaces/trigger_factory.hpp
+++ b/src/interfaces/trigger_factory.hpp
@@ -20,7 +20,8 @@
     virtual ~TriggerFactory() = default;
 
     virtual std::unique_ptr<interfaces::Trigger> make(
-        const std::string& name, const std::vector<std::string>& triggerActions,
+        const std::string& id, const std::string& name,
+        const std::vector<std::string>& triggerActions,
         const std::vector<std::string>& reportNames,
         interfaces::TriggerManager& triggerManager,
         interfaces::JsonStorage& triggerStorage,
diff --git a/src/report.hpp b/src/report.hpp
index 26cc130..18574cf 100644
--- a/src/report.hpp
+++ b/src/report.hpp
@@ -50,8 +50,8 @@
     static void timerProc(boost::system::error_code, Report& self);
     void scheduleTimer(Milliseconds interval);
 
-    const std::string name;
-    const std::string path;
+    std::string name;
+    std::string path;
     std::string reportingType;
     Milliseconds interval;
     bool emitsReadingsUpdate;
diff --git a/src/report_manager.cpp b/src/report_manager.cpp
index c8103a6..64c587e 100644
--- a/src/report_manager.cpp
+++ b/src/report_manager.cpp
@@ -101,7 +101,7 @@
     {
         throw sdbusplus::exception::SdBusError(
             static_cast<int>(std::errc::invalid_argument),
-            "Report name exceed maximum length");
+            "Report name exceeds maximum length");
     }
 }
 
diff --git a/src/report_manager.hpp b/src/report_manager.hpp
index f056f78..9c21c5d 100644
--- a/src/report_manager.hpp
+++ b/src/report_manager.hpp
@@ -4,6 +4,7 @@
 #include "interfaces/report.hpp"
 #include "interfaces/report_factory.hpp"
 #include "interfaces/report_manager.hpp"
+#include "report.hpp"
 
 #include <systemd/sd-bus-protocol.h>
 
@@ -59,7 +60,8 @@
     static constexpr size_t maxReports{TELEMETRY_MAX_REPORTS};
     static constexpr size_t maxReadingParams{TELEMETRY_MAX_READING_PARAMS};
     static constexpr size_t maxReportNameLength{
-        TELEMETRY_MAX_REPORT_NAME_LENGTH};
+        TELEMETRY_MAX_DBUS_PATH_LENGTH -
+        std::string_view(Report::reportDir).length()};
     static constexpr Milliseconds minInterval{TELEMETRY_MIN_INTERVAL};
     static constexpr const char* reportManagerIfaceName =
         "xyz.openbmc_project.Telemetry.ReportManager";
diff --git a/src/trigger.cpp b/src/trigger.cpp
index 92e1472..ff229b8 100644
--- a/src/trigger.cpp
+++ b/src/trigger.cpp
@@ -1,5 +1,6 @@
 #include "trigger.hpp"
 
+#include "trigger_manager.hpp"
 #include "types/report_types.hpp"
 #include "types/trigger_types.hpp"
 #include "utils/conversion_trigger.hpp"
@@ -10,19 +11,21 @@
 Trigger::Trigger(
     boost::asio::io_context& ioc,
     const std::shared_ptr<sdbusplus::asio::object_server>& objServer,
-    const std::string& nameIn, const std::vector<std::string>& triggerActionsIn,
+    const std::string& idIn, const std::string& nameIn,
+    const std::vector<std::string>& triggerActionsIn,
     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),
-    triggerActions(std::move(triggerActionsIn)), path(triggerDir + name),
-    reportNames(reportNamesIn), labeledSensorsInfo(LabeledSensorsInfoIn),
+    id(idIn),
+    name(nameIn), triggerActions(std::move(triggerActionsIn)),
+    path(triggerDir + id), reportNames(reportNamesIn),
+    labeledSensorsInfo(LabeledSensorsInfoIn),
     labeledThresholdParams(labeledThresholdParamsIn),
     thresholds(std::move(thresholdsIn)),
-    fileName(std::to_string(std::hash<std::string>{}(name))),
+    fileName(std::to_string(std::hash<std::string>{}(id))),
     triggerStorage(triggerStorageIn)
 {
     deleteIface = objServer->add_unique_interface(
@@ -89,6 +92,15 @@
                     return isTriggerThresholdDiscrete(labeledThresholdParams);
                 });
 
+            dbusIface.register_property_rw(
+                "Name", std::string(),
+                sdbusplus::vtable::property_::emits_change,
+                [this](auto newVal, auto& oldVal) {
+                    name = oldVal = newVal;
+                    return true;
+                },
+                [this](const auto&) { return name; });
+
             dbusIface.register_property_r("TriggerActions", triggerActions,
                                           sdbusplus::vtable::property_::const_,
                                           [this](const auto& x) { return x; });
@@ -107,6 +119,7 @@
         nlohmann::json data;
 
         data["Version"] = triggerVersion;
+        data["Id"] = id;
         data["Name"] = name;
         data["ThresholdParamsDiscriminator"] = labeledThresholdParams.index();
         data["TriggerActions"] = triggerActions;
diff --git a/src/trigger.hpp b/src/trigger.hpp
index 904f520..e1874e7 100644
--- a/src/trigger.hpp
+++ b/src/trigger.hpp
@@ -16,7 +16,7 @@
   public:
     Trigger(boost::asio::io_context& ioc,
             const std::shared_ptr<sdbusplus::asio::object_server>& objServer,
-            const std::string& name,
+            const std::string& id, const std::string& name,
             const std::vector<std::string>& triggerActions,
             const std::vector<std::string>& reportNames,
             const std::vector<LabeledSensorInfo>& LabeledSensorsInfoIn,
@@ -30,9 +30,9 @@
     Trigger& operator=(const Trigger&) = delete;
     Trigger& operator=(Trigger&&) = delete;
 
-    std::string getName() const override
+    std::string getId() const override
     {
-        return name;
+        return id;
     }
 
     std::string getPath() const override
@@ -43,9 +43,10 @@
     bool storeConfiguration() const;
 
   private:
-    const std::string name;
+    std::string id;
+    std::string name;
     std::vector<std::string> triggerActions;
-    const std::string path;
+    std::string path;
     bool persistent = false;
     std::vector<std::string> reportNames;
     std::vector<LabeledSensorInfo> labeledSensorsInfo;
diff --git a/src/trigger_factory.cpp b/src/trigger_factory.cpp
index 98cfefe..8980cc9 100644
--- a/src/trigger_factory.cpp
+++ b/src/trigger_factory.cpp
@@ -21,7 +21,8 @@
 {}
 
 std::unique_ptr<interfaces::Trigger> TriggerFactory::make(
-    const std::string& name, const std::vector<std::string>& triggerActionsIn,
+    const std::string& id, const std::string& name,
+    const std::vector<std::string>& triggerActionsIn,
     const std::vector<std::string>& reportNames,
     interfaces::TriggerManager& triggerManager,
     interfaces::JsonStorage& triggerStorage,
@@ -100,9 +101,9 @@
     }
 
     return std::make_unique<Trigger>(
-        bus->get_io_context(), objServer, name, triggerActionsIn, reportNames,
-        labeledSensorsInfo, labeledThresholdParams, std::move(thresholds),
-        triggerManager, triggerStorage);
+        bus->get_io_context(), objServer, id, name, triggerActionsIn,
+        reportNames, labeledSensorsInfo, labeledThresholdParams,
+        std::move(thresholds), triggerManager, triggerStorage);
 }
 
 std::pair<Sensors, std::vector<std::string>> TriggerFactory::getSensors(
diff --git a/src/trigger_factory.hpp b/src/trigger_factory.hpp
index e7f8fb0..757d663 100644
--- a/src/trigger_factory.hpp
+++ b/src/trigger_factory.hpp
@@ -16,7 +16,7 @@
                    interfaces::ReportManager& reportManager);
 
     std::unique_ptr<interfaces::Trigger>
-        make(const std::string& name,
+        make(const std::string& id, const std::string& name,
              const std::vector<std::string>& triggerActions,
              const std::vector<std::string>& reportNames,
              interfaces::TriggerManager& triggerManager,
diff --git a/src/trigger_manager.cpp b/src/trigger_manager.cpp
index da4797f..ac0fba5 100644
--- a/src/trigger_manager.cpp
+++ b/src/trigger_manager.cpp
@@ -20,7 +20,7 @@
         triggerManagerPath, triggerManagerIfaceName, [this](auto& iface) {
             iface.register_method(
                 "AddTrigger",
-                [this](boost::asio::yield_context& yield,
+                [this](boost::asio::yield_context& yield, const std::string& id,
                        const std::string& name,
                        const std::vector<std::string>& triggerActions,
                        const SensorsInfo& sensors,
@@ -34,8 +34,8 @@
                     std::vector<LabeledSensorInfo> labeledSensorsInfo =
                         triggerFactory->getLabeledSensorsInfo(yield, sensors);
 
-                    return addTrigger(name, triggerActions, labeledSensorsInfo,
-                                      reportNames,
+                    return addTrigger(id, name, triggerActions,
+                                      labeledSensorsInfo, reportNames,
                                       labeledTriggerThresholdParams)
                         .getPath();
                 });
@@ -50,7 +50,8 @@
         triggers.end());
 }
 
-void TriggerManager::verifyAddTrigger(const std::string& triggerName)
+void TriggerManager::verifyAddTrigger(const std::string& triggerId,
+                                      const std::string& triggerName) const
 {
     if (triggers.size() >= maxTriggers)
     {
@@ -59,9 +60,12 @@
             "Reached maximal trigger count");
     }
 
+    verifyTriggerIdLength(triggerId);
+    verifyIdCharacters(triggerId);
+
     for (const auto& trigger : triggers)
     {
-        if (trigger->getName() == triggerName)
+        if (trigger->getId() == triggerId)
         {
             throw sdbusplus::exception::SdBusError(
                 static_cast<int>(std::errc::file_exists), "Duplicate trigger");
@@ -69,18 +73,79 @@
     }
 }
 
+void TriggerManager::verifyTriggerIdLength(const std::string& triggerId)
+{
+    if (triggerId.length() > maxTriggerIdLength)
+    {
+        throw sdbusplus::exception::SdBusError(
+            static_cast<int>(std::errc::invalid_argument),
+            "Trigger id exceeds maximum length");
+    }
+}
+
+void TriggerManager::verifyIdCharacters(const std::string& triggerId)
+{
+    if (triggerId.find_first_not_of(allowedCharactersInId) != std::string::npos)
+    {
+        throw sdbusplus::exception::SdBusError(
+            static_cast<int>(std::errc::invalid_argument),
+            "Invalid character in trigger id");
+    }
+}
+
+std::string TriggerManager::generateId(const std::string& prefix,
+                                       const std::string& triggerName) const
+{
+    verifyIdCharacters(prefix);
+    std::string strippedId(triggerName);
+    strippedId.erase(std::remove_if(strippedId.begin(), strippedId.end(),
+                                    [](char c) {
+                                        return c == '/' ||
+                                               allowedCharactersInId.find(c) ==
+                                                   std::string_view::npos;
+                                    }),
+                     strippedId.end());
+
+    strippedId = prefix + strippedId;
+    strippedId = strippedId.substr(
+        0, maxTriggerIdLength - std::to_string(maxTriggers - 1).length());
+
+    size_t idx = 0;
+    std::string tmpId(strippedId);
+    while (std::find_if(triggers.begin(), triggers.end(),
+                        [&tmpId](const auto& trigger) {
+                            return trigger->getId() == tmpId;
+                        }) != triggers.end())
+    {
+        tmpId = strippedId + std::to_string(idx++);
+    }
+    return tmpId;
+}
+
 interfaces::Trigger& TriggerManager::addTrigger(
-    const std::string& triggerName,
+    const std::string& triggerIdIn, const std::string& triggerNameIn,
     const std::vector<std::string>& triggerActions,
     const std::vector<LabeledSensorInfo>& labeledSensorsInfo,
     const std::vector<std::string>& reportNames,
     const LabeledTriggerThresholdParams& labeledThresholdParams)
 {
-    verifyAddTrigger(triggerName);
+    std::string triggerName = triggerNameIn;
+    if (triggerName.empty())
+    {
+        triggerName = triggerNameDefault;
+    }
+
+    std::string triggerId = triggerIdIn;
+    if (triggerId.empty() || triggerId.ends_with('/'))
+    {
+        triggerId = generateId(triggerId, triggerName);
+    }
+
+    verifyAddTrigger(triggerId, triggerName);
 
     triggers.emplace_back(triggerFactory->make(
-        triggerName, triggerActions, reportNames, *this, *triggerStorage,
-        labeledThresholdParams, labeledSensorsInfo));
+        triggerId, triggerName, triggerActions, reportNames, *this,
+        *triggerStorage, labeledThresholdParams, labeledSensorsInfo));
 
     return *triggers.back();
 }
@@ -104,6 +169,7 @@
             {
                 throw std::runtime_error("Invalid version");
             }
+            const std::string& id = data->at("Id").get_ref<std::string&>();
             const std::string& name = data->at("Name").get_ref<std::string&>();
             int thresholdParamsDiscriminator =
                 data->at("ThresholdParamsDiscriminator").get<int>();
@@ -130,8 +196,8 @@
             auto labeledSensorsInfo =
                 data->at("Sensors").get<std::vector<LabeledSensorInfo>>();
 
-            addTrigger(name, triggerActions, labeledSensorsInfo, reportNames,
-                       labeledThresholdParams);
+            addTrigger(id, name, triggerActions, labeledSensorsInfo,
+                       reportNames, labeledThresholdParams);
         }
         catch (const std::exception& e)
         {
diff --git a/src/trigger_manager.hpp b/src/trigger_manager.hpp
index c3acd36..4837062 100644
--- a/src/trigger_manager.hpp
+++ b/src/trigger_manager.hpp
@@ -3,6 +3,7 @@
 #include "interfaces/report_manager.hpp"
 #include "interfaces/trigger_factory.hpp"
 #include "interfaces/trigger_manager.hpp"
+#include "trigger.hpp"
 
 #include <sdbusplus/asio/object_server.hpp>
 
@@ -30,10 +31,15 @@
     std::unique_ptr<sdbusplus::asio::dbus_interface> managerIface;
     std::vector<std::unique_ptr<interfaces::Trigger>> triggers;
 
-    void verifyAddTrigger(const std::string& triggerName);
+    void verifyAddTrigger(const std::string& triggerId,
+                          const std::string& triggerName) const;
+    std::string generateId(const std::string& prefix,
+                           const std::string& triggerName) const;
+    static void verifyTriggerIdLength(const std::string& triggerId);
+    static void verifyIdCharacters(const std::string& triggerId);
 
     interfaces::Trigger&
-        addTrigger(const std::string& triggerName,
+        addTrigger(const std::string& triggerId, const std::string& triggerName,
                    const std::vector<std::string>& triggerActions,
                    const std::vector<LabeledSensorInfo>& labeledSensors,
                    const std::vector<std::string>& reportNames,
@@ -42,8 +48,15 @@
 
   public:
     static constexpr size_t maxTriggers{TELEMETRY_MAX_TRIGGERS};
+    static constexpr size_t maxTriggerIdLength{
+        TELEMETRY_MAX_DBUS_PATH_LENGTH -
+        std::string_view(Trigger::triggerDir).length()};
     static constexpr const char* triggerManagerIfaceName =
         "xyz.openbmc_project.Telemetry.TriggerManager";
     static constexpr const char* triggerManagerPath =
         "/xyz/openbmc_project/Telemetry/Triggers";
+
+    static constexpr std::string_view allowedCharactersInId =
+        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_/";
+    static constexpr const char* triggerNameDefault = "Trigger";
 };