xref: /openbmc/bmcweb/redfish-core/lib/trigger.hpp (revision e3648032f357195d186b7f4f02519fae10a90e41)
1 #pragma once
2 
3 #include "app.hpp"
4 #include "generated/enums/metric_definition.hpp"
5 #include "generated/enums/resource.hpp"
6 #include "generated/enums/triggers.hpp"
7 #include "query.hpp"
8 #include "registries/privilege_registry.hpp"
9 #include "utility.hpp"
10 #include "utils/collection.hpp"
11 #include "utils/dbus_utils.hpp"
12 #include "utils/json_utils.hpp"
13 #include "utils/sensor_utils.hpp"
14 #include "utils/telemetry_utils.hpp"
15 #include "utils/time_utils.hpp"
16 
17 #include <boost/url/format.hpp>
18 #include <sdbusplus/asio/property.hpp>
19 #include <sdbusplus/unpack_properties.hpp>
20 
21 #include <array>
22 #include <string_view>
23 #include <tuple>
24 #include <variant>
25 #include <vector>
26 
27 namespace redfish
28 {
29 namespace telemetry
30 {
31 constexpr const char* triggerInterface =
32     "xyz.openbmc_project.Telemetry.Trigger";
33 
34 using NumericThresholdParams =
35     std::tuple<std::string, uint64_t, std::string, double>;
36 
37 using DiscreteThresholdParams =
38     std::tuple<std::string, std::string, uint64_t, std::string>;
39 
40 using TriggerThresholdParams =
41     std::variant<std::vector<NumericThresholdParams>,
42                  std::vector<DiscreteThresholdParams>>;
43 
44 using TriggerSensorsParams =
45     std::vector<std::pair<sdbusplus::message::object_path, std::string>>;
46 
47 inline triggers::TriggerActionEnum
48     toRedfishTriggerAction(std::string_view dbusValue)
49 {
50     if (dbusValue ==
51         "xyz.openbmc_project.Telemetry.Trigger.TriggerAction.UpdateReport")
52     {
53         return triggers::TriggerActionEnum::RedfishMetricReport;
54     }
55     if (dbusValue ==
56         "xyz.openbmc_project.Telemetry.Trigger.TriggerAction.LogToRedfishEventLog")
57     {
58         return triggers::TriggerActionEnum::RedfishEvent;
59     }
60     if (dbusValue ==
61         "xyz.openbmc_project.Telemetry.Trigger.TriggerAction.LogToJournal")
62     {
63         return triggers::TriggerActionEnum::LogToLogService;
64     }
65     return triggers::TriggerActionEnum::Invalid;
66 }
67 
68 inline std::string toDbusTriggerAction(std::string_view redfishValue)
69 {
70     if (redfishValue == "RedfishMetricReport")
71     {
72         return "xyz.openbmc_project.Telemetry.Trigger.TriggerAction.UpdateReport";
73     }
74     if (redfishValue == "RedfishEvent")
75     {
76         return "xyz.openbmc_project.Telemetry.Trigger.TriggerAction.LogToRedfishEventLog";
77     }
78     if (redfishValue == "LogToLogService")
79     {
80         return "xyz.openbmc_project.Telemetry.Trigger.TriggerAction.LogToJournal";
81     }
82     return "";
83 }
84 
85 inline std::string toDbusSeverity(std::string_view redfishValue)
86 {
87     if (redfishValue == "OK")
88     {
89         return "xyz.openbmc_project.Telemetry.Trigger.Severity.OK";
90     }
91     if (redfishValue == "Warning")
92     {
93         return "xyz.openbmc_project.Telemetry.Trigger.Severity.Warning";
94     }
95     if (redfishValue == "Critical")
96     {
97         return "xyz.openbmc_project.Telemetry.Trigger.Severity.Critical";
98     }
99     return "";
100 }
101 
102 inline resource::Health toRedfishSeverity(std::string_view dbusValue)
103 {
104     if (dbusValue == "xyz.openbmc_project.Telemetry.Trigger.Severity.OK")
105     {
106         return resource::Health::OK;
107     }
108     if (dbusValue == "xyz.openbmc_project.Telemetry.Trigger.Severity.Warning")
109     {
110         return resource::Health::Warning;
111     }
112     if (dbusValue == "xyz.openbmc_project.Telemetry.Trigger.Severity.Critical")
113     {
114         return resource::Health::Critical;
115     }
116     return resource::Health::Invalid;
117 }
118 
119 inline std::string toRedfishThresholdName(std::string_view dbusValue)
120 {
121     if (dbusValue == "xyz.openbmc_project.Telemetry.Trigger.Type.UpperCritical")
122     {
123         return "UpperCritical";
124     }
125 
126     if (dbusValue == "xyz.openbmc_project.Telemetry.Trigger.Type.LowerCritical")
127     {
128         return "LowerCritical";
129     }
130 
131     if (dbusValue == "xyz.openbmc_project.Telemetry.Trigger.Type.UpperWarning")
132     {
133         return "UpperWarning";
134     }
135 
136     if (dbusValue == "xyz.openbmc_project.Telemetry.Trigger.Type.LowerWarning")
137     {
138         return "LowerWarning";
139     }
140 
141     return "";
142 }
143 
144 inline std::string toDbusActivation(std::string_view redfishValue)
145 {
146     if (redfishValue == "Either")
147     {
148         return "xyz.openbmc_project.Telemetry.Trigger.Direction.Either";
149     }
150 
151     if (redfishValue == "Decreasing")
152     {
153         return "xyz.openbmc_project.Telemetry.Trigger.Direction.Decreasing";
154     }
155 
156     if (redfishValue == "Increasing")
157     {
158         return "xyz.openbmc_project.Telemetry.Trigger.Direction.Increasing";
159     }
160 
161     return "";
162 }
163 
164 inline triggers::ThresholdActivation
165     toRedfishActivation(std::string_view dbusValue)
166 {
167     if (dbusValue == "xyz.openbmc_project.Telemetry.Trigger.Direction.Either")
168     {
169         return triggers::ThresholdActivation::Either;
170     }
171 
172     if (dbusValue ==
173         "xyz.openbmc_project.Telemetry.Trigger.Direction.Decreasing")
174     {
175         return triggers::ThresholdActivation::Decreasing;
176     }
177 
178     if (dbusValue ==
179         "xyz.openbmc_project.Telemetry.Trigger.Direction.Increasing")
180     {
181         return triggers::ThresholdActivation::Increasing;
182     }
183 
184     return triggers::ThresholdActivation::Invalid;
185 }
186 
187 enum class MetricType
188 {
189     Discrete,
190     Numeric
191 };
192 
193 enum class DiscreteCondition
194 {
195     Specified,
196     Changed
197 };
198 
199 struct Context
200 {
201     std::string id;
202     std::string name;
203     std::vector<std::string> actions;
204     std::vector<std::pair<sdbusplus::message::object_path, std::string>>
205         sensors;
206     std::vector<sdbusplus::message::object_path> reports;
207     TriggerThresholdParams thresholds;
208 
209     std::optional<DiscreteCondition> discreteCondition;
210     std::optional<MetricType> metricType;
211     std::optional<std::vector<std::string>> metricProperties;
212 };
213 
214 inline std::optional<sdbusplus::message::object_path>
215     getReportPathFromReportDefinitionUri(const std::string& uri)
216 {
217     boost::system::result<boost::urls::url_view> parsed =
218         boost::urls::parse_relative_ref(uri);
219 
220     if (!parsed)
221     {
222         return std::nullopt;
223     }
224 
225     std::string id;
226     if (!crow::utility::readUrlSegments(
227             *parsed, "redfish", "v1", "TelemetryService",
228             "MetricReportDefinitions", std::ref(id)))
229     {
230         return std::nullopt;
231     }
232 
233     return sdbusplus::message::object_path(
234                "/xyz/openbmc_project/Telemetry/Reports") /
235            "TelemetryService" / id;
236 }
237 
238 inline std::optional<MetricType> getMetricType(const std::string& metricType)
239 {
240     if (metricType == "Discrete")
241     {
242         return MetricType::Discrete;
243     }
244     if (metricType == "Numeric")
245     {
246         return MetricType::Numeric;
247     }
248     return std::nullopt;
249 }
250 
251 inline std::optional<DiscreteCondition>
252     getDiscreteCondition(const std::string& discreteTriggerCondition)
253 {
254     if (discreteTriggerCondition == "Specified")
255     {
256         return DiscreteCondition::Specified;
257     }
258     if (discreteTriggerCondition == "Changed")
259     {
260         return DiscreteCondition::Changed;
261     }
262     return std::nullopt;
263 }
264 
265 inline bool parseThreshold(crow::Response& res,
266                            nlohmann::json::object_t& threshold,
267                            std::string_view dbusThresholdName,
268                            std::vector<NumericThresholdParams>& parsedParams)
269 {
270     double reading = 0.0;
271     std::string activation;
272     std::string dwellTimeStr;
273 
274     if (!json_util::readJsonObject( //
275             threshold, res, //
276             "Activation", activation, //
277             "DwellTime", dwellTimeStr, //
278             "Reading", reading //
279             ))
280     {
281         return false;
282     }
283 
284     std::string dbusActivation = toDbusActivation(activation);
285 
286     if (dbusActivation.empty())
287     {
288         messages::propertyValueIncorrect(res, "Activation", activation);
289         return false;
290     }
291 
292     std::optional<std::chrono::milliseconds> dwellTime =
293         time_utils::fromDurationString(dwellTimeStr);
294     if (!dwellTime)
295     {
296         messages::propertyValueIncorrect(res, "DwellTime", dwellTimeStr);
297         return false;
298     }
299 
300     parsedParams.emplace_back(dbusThresholdName,
301                               static_cast<uint64_t>(dwellTime->count()),
302                               dbusActivation, reading);
303     return true;
304 }
305 
306 struct NumericThresholds
307 {
308     std::optional<nlohmann::json::object_t> upperCritical;
309     std::optional<nlohmann::json::object_t> upperWarning;
310     std::optional<nlohmann::json::object_t> lowerWarning;
311     std::optional<nlohmann::json::object_t> lowerCritical;
312 
313     bool any() const
314     {
315         return upperCritical || upperWarning || lowerWarning || lowerCritical;
316     }
317 };
318 
319 inline bool parseNumericThresholds(
320     crow::Response& res, NumericThresholds& numericThresholds, Context& ctx)
321 {
322     std::vector<NumericThresholdParams> parsedParams;
323     if (numericThresholds.upperCritical)
324     {
325         if (!parseThreshold(
326                 res, *numericThresholds.upperCritical,
327                 "xyz.openbmc_project.Telemetry.Trigger.Type.UpperCritical",
328                 parsedParams))
329         {
330             return false;
331         }
332     }
333     if (numericThresholds.upperWarning)
334     {
335         if (!parseThreshold(
336                 res, *numericThresholds.upperWarning,
337                 "xyz.openbmc_project.Telemetry.Trigger.Type.UpperWarning",
338                 parsedParams))
339         {
340             return false;
341         }
342     }
343     if (numericThresholds.lowerWarning)
344     {
345         if (!parseThreshold(
346                 res, *numericThresholds.lowerWarning,
347                 "xyz.openbmc_project.Telemetry.Trigger.Type.LowerWarning",
348                 parsedParams))
349         {
350             return false;
351         }
352     }
353     if (numericThresholds.lowerCritical)
354     {
355         if (!parseThreshold(
356                 res, *numericThresholds.lowerCritical,
357                 "xyz.openbmc_project.Telemetry.Trigger.Type.LowerCritical",
358                 parsedParams))
359         {
360             return false;
361         }
362     }
363 
364     ctx.thresholds = std::move(parsedParams);
365     return true;
366 }
367 
368 inline bool parseDiscreteTriggers(
369     crow::Response& res,
370     std::optional<std::vector<nlohmann::json::object_t>>& discreteTriggers,
371     Context& ctx)
372 {
373     std::vector<DiscreteThresholdParams> parsedParams;
374     if (!discreteTriggers)
375     {
376         ctx.thresholds = std::move(parsedParams);
377         return true;
378     }
379 
380     parsedParams.reserve(discreteTriggers->size());
381     for (nlohmann::json::object_t& thresholdInfo : *discreteTriggers)
382     {
383         std::optional<std::string> name = "";
384         std::string value;
385         std::string dwellTimeStr;
386         std::string severity;
387 
388         if (!json_util::readJsonObject( //
389                 thresholdInfo, res, //
390                 "DwellTime", dwellTimeStr, //
391                 "Name", name, //
392                 "Severity", severity, //
393                 "Value", value //
394                 ))
395         {
396             return false;
397         }
398 
399         std::optional<std::chrono::milliseconds> dwellTime =
400             time_utils::fromDurationString(dwellTimeStr);
401         if (!dwellTime)
402         {
403             messages::propertyValueIncorrect(res, "DwellTime", dwellTimeStr);
404             return false;
405         }
406 
407         std::string dbusSeverity = toDbusSeverity(severity);
408         if (dbusSeverity.empty())
409         {
410             messages::propertyValueIncorrect(res, "Severity", severity);
411             return false;
412         }
413 
414         parsedParams.emplace_back(*name, dbusSeverity,
415                                   static_cast<uint64_t>(dwellTime->count()),
416                                   value);
417     }
418 
419     ctx.thresholds = std::move(parsedParams);
420     return true;
421 }
422 
423 inline bool parseTriggerThresholds(
424     crow::Response& res,
425     std::optional<std::vector<nlohmann::json::object_t>>& discreteTriggers,
426     NumericThresholds& numericThresholds, Context& ctx)
427 {
428     if (discreteTriggers && numericThresholds.any())
429     {
430         messages::propertyValueConflict(res, "DiscreteTriggers",
431                                         "NumericThresholds");
432         messages::propertyValueConflict(res, "NumericThresholds",
433                                         "DiscreteTriggers");
434         return false;
435     }
436 
437     if (ctx.discreteCondition)
438     {
439         if (numericThresholds.any())
440         {
441             messages::propertyValueConflict(res, "DiscreteTriggerCondition",
442                                             "NumericThresholds");
443             messages::propertyValueConflict(res, "NumericThresholds",
444                                             "DiscreteTriggerCondition");
445             return false;
446         }
447     }
448 
449     if (ctx.metricType)
450     {
451         if (*ctx.metricType == MetricType::Discrete && numericThresholds.any())
452         {
453             messages::propertyValueConflict(res, "NumericThresholds",
454                                             "MetricType");
455             return false;
456         }
457         if (*ctx.metricType == MetricType::Numeric && discreteTriggers)
458         {
459             messages::propertyValueConflict(res, "DiscreteTriggers",
460                                             "MetricType");
461             return false;
462         }
463         if (*ctx.metricType == MetricType::Numeric && ctx.discreteCondition)
464         {
465             messages::propertyValueConflict(res, "DiscreteTriggers",
466                                             "DiscreteTriggerCondition");
467             return false;
468         }
469     }
470 
471     if (discreteTriggers || ctx.discreteCondition ||
472         (ctx.metricType && *ctx.metricType == MetricType::Discrete))
473     {
474         if (ctx.discreteCondition)
475         {
476             if (*ctx.discreteCondition == DiscreteCondition::Specified &&
477                 !discreteTriggers)
478             {
479                 messages::createFailedMissingReqProperties(res,
480                                                            "DiscreteTriggers");
481                 return false;
482             }
483             if (discreteTriggers &&
484                 ((*ctx.discreteCondition == DiscreteCondition::Specified &&
485                   discreteTriggers->empty()) ||
486                  (*ctx.discreteCondition == DiscreteCondition::Changed &&
487                   !discreteTriggers->empty())))
488             {
489                 messages::propertyValueConflict(res, "DiscreteTriggers",
490                                                 "DiscreteTriggerCondition");
491                 return false;
492             }
493         }
494         if (!parseDiscreteTriggers(res, discreteTriggers, ctx))
495         {
496             return false;
497         }
498     }
499     else if (numericThresholds.any())
500     {
501         if (!parseNumericThresholds(res, numericThresholds, ctx))
502         {
503             return false;
504         }
505     }
506     else
507     {
508         messages::createFailedMissingReqProperties(
509             res, "'DiscreteTriggers', 'NumericThresholds', "
510                  "'DiscreteTriggerCondition' or 'MetricType'");
511         return false;
512     }
513     return true;
514 }
515 
516 inline bool parseLinks(crow::Response& res,
517                        const std::vector<std::string>& metricReportDefinitions,
518                        Context& ctx)
519 {
520     ctx.reports.reserve(metricReportDefinitions.size());
521     for (const std::string& reportDefinionUri : metricReportDefinitions)
522     {
523         std::optional<sdbusplus::message::object_path> reportPath =
524             getReportPathFromReportDefinitionUri(reportDefinionUri);
525         if (!reportPath)
526         {
527             messages::propertyValueIncorrect(res, "MetricReportDefinitions",
528                                              reportDefinionUri);
529             return false;
530         }
531         ctx.reports.emplace_back(*reportPath);
532     }
533     return true;
534 }
535 
536 inline bool parseMetricProperties(crow::Response& res, Context& ctx)
537 {
538     if (!ctx.metricProperties)
539     {
540         return true;
541     }
542 
543     ctx.sensors.reserve(ctx.metricProperties->size());
544 
545     size_t uriIdx = 0;
546     for (const std::string& uriStr : *ctx.metricProperties)
547     {
548         boost::system::result<boost::urls::url> uri =
549             boost::urls::parse_relative_ref(uriStr);
550         if (!uri)
551         {
552             messages::propertyValueIncorrect(
553                 res, "MetricProperties/" + std::to_string(uriIdx), uriStr);
554             return false;
555         }
556         std::string chassisName;
557         std::string sensorName;
558         if (!crow::utility::readUrlSegments(*uri, "redfish", "v1", "Chassis",
559                                             std::ref(chassisName), "Sensors",
560                                             std::ref(sensorName)))
561         {
562             messages::propertyValueIncorrect(
563                 res, "MetricProperties/" + std::to_string(uriIdx), uriStr);
564             return false;
565         }
566 
567         std::pair<std::string, std::string> split =
568             redfish::sensor_utils::splitSensorNameAndType(sensorName);
569         if (split.first.empty() || split.second.empty())
570         {
571             messages::propertyValueIncorrect(
572                 res, "MetricProperties/" + std::to_string(uriIdx), uriStr);
573             return false;
574         }
575 
576         std::string sensorPath =
577             "/xyz/openbmc_project/sensors/" + split.first + '/' + split.second;
578 
579         ctx.sensors.emplace_back(sensorPath, uriStr);
580         uriIdx++;
581     }
582     return true;
583 }
584 
585 inline bool parsePostTriggerParams(crow::Response& res,
586                                    const crow::Request& req, Context& ctx)
587 {
588     std::optional<std::string> id = "";
589     std::optional<std::string> name = "";
590     std::optional<std::string> metricType;
591     std::optional<std::vector<std::string>> triggerActions;
592     std::optional<std::string> discreteTriggerCondition;
593     std::optional<std::vector<nlohmann::json::object_t>> discreteTriggers;
594     std::optional<std::vector<std::string>> metricReportDefinitions;
595     NumericThresholds thresholds;
596 
597     if (!json_util::readJsonPatch( //
598             req, res, //
599             "Id", id, //
600             "DiscreteTriggerCondition", discreteTriggerCondition, //
601             "DiscreteTriggers", discreteTriggers, //
602             "Links/MetricReportDefinitions", metricReportDefinitions, //
603             "MetricProperties", ctx.metricProperties, //
604             "MetricType", metricType, //
605             "Name", name, //
606             "NumericThresholds/LowerCritical", thresholds.lowerCritical, //
607             "NumericThresholds/LowerWarning", thresholds.lowerWarning, //
608             "NumericThresholds/UpperCritical", thresholds.upperCritical, //
609             "NumericThresholds/UpperWarning", thresholds.upperWarning, //
610             "TriggerActions", triggerActions //
611             ))
612     {
613         return false;
614     }
615 
616     ctx.id = *id;
617     ctx.name = *name;
618 
619     if (metricType)
620     {
621         ctx.metricType = getMetricType(*metricType);
622         if (!ctx.metricType)
623         {
624             messages::propertyValueIncorrect(res, "MetricType", *metricType);
625             return false;
626         }
627     }
628 
629     if (discreteTriggerCondition)
630     {
631         ctx.discreteCondition = getDiscreteCondition(*discreteTriggerCondition);
632         if (!ctx.discreteCondition)
633         {
634             messages::propertyValueIncorrect(res, "DiscreteTriggerCondition",
635                                              *discreteTriggerCondition);
636             return false;
637         }
638     }
639 
640     if (triggerActions)
641     {
642         ctx.actions.reserve(triggerActions->size());
643         for (const std::string& action : *triggerActions)
644         {
645             std::string dbusAction = toDbusTriggerAction(action);
646 
647             if (dbusAction.empty())
648             {
649                 messages::propertyValueNotInList(res, action, "TriggerActions");
650                 return false;
651             }
652 
653             ctx.actions.emplace_back(dbusAction);
654         }
655     }
656     if (!parseMetricProperties(res, ctx))
657     {
658         return false;
659     }
660 
661     if (!parseTriggerThresholds(res, discreteTriggers, thresholds, ctx))
662     {
663         return false;
664     }
665 
666     if (metricReportDefinitions)
667     {
668         if (!parseLinks(res, *metricReportDefinitions, ctx))
669         {
670             return false;
671         }
672     }
673     return true;
674 }
675 
676 inline void afterCreateTrigger(
677     const boost::system::error_code& ec, const std::string& dbusPath,
678     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
679 {
680     if (ec == boost::system::errc::file_exists)
681     {
682         messages::resourceAlreadyExists(asyncResp->res, "Trigger", "Id", id);
683         return;
684     }
685     if (ec == boost::system::errc::too_many_files_open)
686     {
687         messages::createLimitReachedForResource(asyncResp->res);
688         return;
689     }
690     if (ec)
691     {
692         messages::internalError(asyncResp->res);
693         BMCWEB_LOG_ERROR("respHandler DBus error {}", ec);
694         return;
695     }
696 
697     const std::optional<std::string>& triggerId =
698         getTriggerIdFromDbusPath(dbusPath);
699     if (!triggerId)
700     {
701         messages::internalError(asyncResp->res);
702         BMCWEB_LOG_ERROR("Unknown data returned by "
703                          "AddTrigger DBus method");
704         return;
705     }
706 
707     messages::created(asyncResp->res);
708     boost::urls::url locationUrl = boost::urls::format(
709         "/redfish/v1/TelemetryService/Triggers/{}", *triggerId);
710     asyncResp->res.addHeader("Location", locationUrl.buffer());
711 }
712 
713 inline std::optional<nlohmann::json::array_t>
714     getTriggerActions(const std::vector<std::string>& dbusActions)
715 {
716     nlohmann::json::array_t triggerActions;
717     for (const std::string& dbusAction : dbusActions)
718     {
719         triggers::TriggerActionEnum redfishAction =
720             toRedfishTriggerAction(dbusAction);
721 
722         if (redfishAction == triggers::TriggerActionEnum::Invalid)
723         {
724             return std::nullopt;
725         }
726 
727         triggerActions.emplace_back(redfishAction);
728     }
729 
730     return triggerActions;
731 }
732 
733 inline std::optional<nlohmann::json::array_t>
734     getDiscreteTriggers(const TriggerThresholdParams& thresholdParams)
735 {
736     nlohmann::json::array_t triggers;
737     const std::vector<DiscreteThresholdParams>* discreteParams =
738         std::get_if<std::vector<DiscreteThresholdParams>>(&thresholdParams);
739 
740     if (discreteParams == nullptr)
741     {
742         return std::nullopt;
743     }
744 
745     for (const auto& [name, severity, dwellTime, value] : *discreteParams)
746     {
747         std::optional<std::string> duration =
748             time_utils::toDurationStringFromUint(dwellTime);
749 
750         if (!duration)
751         {
752             return std::nullopt;
753         }
754         nlohmann::json::object_t trigger;
755         trigger["Name"] = name;
756         trigger["Severity"] = toRedfishSeverity(severity);
757         trigger["DwellTime"] = *duration;
758         trigger["Value"] = value;
759         triggers.emplace_back(std::move(trigger));
760     }
761 
762     return triggers;
763 }
764 
765 inline std::optional<nlohmann::json>
766     getNumericThresholds(const TriggerThresholdParams& thresholdParams)
767 {
768     nlohmann::json::object_t thresholds;
769     const std::vector<NumericThresholdParams>* numericParams =
770         std::get_if<std::vector<NumericThresholdParams>>(&thresholdParams);
771 
772     if (numericParams == nullptr)
773     {
774         return std::nullopt;
775     }
776 
777     for (const auto& [type, dwellTime, activation, reading] : *numericParams)
778     {
779         std::optional<std::string> duration =
780             time_utils::toDurationStringFromUint(dwellTime);
781 
782         if (!duration)
783         {
784             return std::nullopt;
785         }
786         nlohmann::json& threshold = thresholds[toRedfishThresholdName(type)];
787         threshold["Reading"] = reading;
788         threshold["Activation"] = toRedfishActivation(activation);
789         threshold["DwellTime"] = *duration;
790     }
791 
792     return thresholds;
793 }
794 
795 inline std::optional<nlohmann::json> getMetricReportDefinitions(
796     const std::vector<sdbusplus::message::object_path>& reportPaths)
797 {
798     nlohmann::json reports = nlohmann::json::array();
799 
800     for (const sdbusplus::message::object_path& path : reportPaths)
801     {
802         std::string reportId = path.filename();
803         if (reportId.empty())
804         {
805             {
806                 BMCWEB_LOG_ERROR("Property Reports contains invalid value: {}",
807                                  path.str);
808                 return std::nullopt;
809             }
810         }
811 
812         nlohmann::json::object_t report;
813         report["@odata.id"] = boost::urls::format(
814             "/redfish/v1/TelemetryService/MetricReportDefinitions/{}",
815             reportId);
816         reports.emplace_back(std::move(report));
817     }
818 
819     return {std::move(reports)};
820 }
821 
822 inline std::vector<std::string>
823     getMetricProperties(const TriggerSensorsParams& sensors)
824 {
825     std::vector<std::string> metricProperties;
826     metricProperties.reserve(sensors.size());
827     for (const auto& [_, metadata] : sensors)
828     {
829         metricProperties.emplace_back(metadata);
830     }
831 
832     return metricProperties;
833 }
834 
835 inline bool fillTrigger(nlohmann::json& json, const std::string& id,
836                         const dbus::utility::DBusPropertiesMap& properties)
837 {
838     const std::string* name = nullptr;
839     const bool* discrete = nullptr;
840     const TriggerSensorsParams* sensors = nullptr;
841     const std::vector<sdbusplus::message::object_path>* reports = nullptr;
842     const std::vector<std::string>* triggerActions = nullptr;
843     const TriggerThresholdParams* thresholds = nullptr;
844 
845     const bool success = sdbusplus::unpackPropertiesNoThrow(
846         dbus_utils::UnpackErrorPrinter(), properties, "Name", name, "Discrete",
847         discrete, "Sensors", sensors, "Reports", reports, "TriggerActions",
848         triggerActions, "Thresholds", thresholds);
849 
850     if (!success)
851     {
852         return false;
853     }
854 
855     if (triggerActions != nullptr)
856     {
857         std::optional<nlohmann::json::array_t> redfishTriggerActions =
858             getTriggerActions(*triggerActions);
859         if (!redfishTriggerActions)
860         {
861             BMCWEB_LOG_ERROR(
862                 "Property TriggerActions is invalid in Trigger: {}", id);
863             return false;
864         }
865         json["TriggerActions"] = *redfishTriggerActions;
866     }
867 
868     if (reports != nullptr)
869     {
870         std::optional<nlohmann::json> linkedReports =
871             getMetricReportDefinitions(*reports);
872         if (!linkedReports)
873         {
874             BMCWEB_LOG_ERROR("Property Reports is invalid in Trigger: {}", id);
875             return false;
876         }
877         json["Links"]["MetricReportDefinitions"] = *linkedReports;
878     }
879 
880     if (discrete != nullptr)
881     {
882         if (*discrete)
883         {
884             std::optional<nlohmann::json::array_t> discreteTriggers =
885                 getDiscreteTriggers(*thresholds);
886 
887             if (!discreteTriggers)
888             {
889                 BMCWEB_LOG_ERROR("Property Thresholds is invalid for discrete "
890                                  "triggers in Trigger: {}",
891                                  id);
892                 return false;
893             }
894 
895             json["DiscreteTriggers"] = *discreteTriggers;
896             json["DiscreteTriggerCondition"] =
897                 discreteTriggers->empty() ? "Changed" : "Specified";
898             json["MetricType"] = metric_definition::MetricType::Discrete;
899         }
900         else
901         {
902             std::optional<nlohmann::json> numericThresholds =
903                 getNumericThresholds(*thresholds);
904 
905             if (!numericThresholds)
906             {
907                 BMCWEB_LOG_ERROR("Property Thresholds is invalid for numeric "
908                                  "thresholds in Trigger: {}",
909                                  id);
910                 return false;
911             }
912 
913             json["NumericThresholds"] = *numericThresholds;
914             json["MetricType"] = metric_definition::MetricType::Numeric;
915         }
916     }
917 
918     if (name != nullptr)
919     {
920         json["Name"] = *name;
921     }
922 
923     if (sensors != nullptr)
924     {
925         json["MetricProperties"] = getMetricProperties(*sensors);
926     }
927 
928     json["@odata.type"] = "#Triggers.v1_2_0.Triggers";
929     json["@odata.id"] =
930         boost::urls::format("/redfish/v1/TelemetryService/Triggers/{}", id);
931     json["Id"] = id;
932 
933     return true;
934 }
935 
936 inline void handleTriggerCollectionPost(
937     App& app, const crow::Request& req,
938     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
939 {
940     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
941     {
942         return;
943     }
944 
945     telemetry::Context ctx;
946     if (!telemetry::parsePostTriggerParams(asyncResp->res, req, ctx))
947     {
948         return;
949     }
950 
951     crow::connections::systemBus->async_method_call(
952         [asyncResp, id = ctx.id](const boost::system::error_code& ec,
953                                  const std::string& dbusPath) {
954             afterCreateTrigger(ec, dbusPath, asyncResp, id);
955         },
956         service, "/xyz/openbmc_project/Telemetry/Triggers",
957         "xyz.openbmc_project.Telemetry.TriggerManager", "AddTrigger",
958         "TelemetryService/" + ctx.id, ctx.name, ctx.actions, ctx.sensors,
959         ctx.reports, ctx.thresholds);
960 }
961 
962 } // namespace telemetry
963 
964 inline void requestRoutesTriggerCollection(App& app)
965 {
966     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/")
967         .privileges(redfish::privileges::getTriggersCollection)
968         .methods(boost::beast::http::verb::get)(
969             [&app](const crow::Request& req,
970                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
971                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
972                 {
973                     return;
974                 }
975                 asyncResp->res.jsonValue["@odata.type"] =
976                     "#TriggersCollection.TriggersCollection";
977                 asyncResp->res.jsonValue["@odata.id"] =
978                     "/redfish/v1/TelemetryService/Triggers";
979                 asyncResp->res.jsonValue["Name"] = "Triggers Collection";
980                 constexpr std::array<std::string_view, 1> interfaces{
981                     telemetry::triggerInterface};
982                 collection_util::getCollectionMembers(
983                     asyncResp,
984                     boost::urls::url("/redfish/v1/TelemetryService/Triggers"),
985                     interfaces,
986                     "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService");
987             });
988 
989     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/")
990         .privileges(redfish::privileges::postTriggersCollection)
991         .methods(boost::beast::http::verb::post)(std::bind_front(
992             telemetry::handleTriggerCollectionPost, std::ref(app)));
993 }
994 
995 inline void requestRoutesTrigger(App& app)
996 {
997     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/")
998         .privileges(redfish::privileges::getTriggers)
999         .methods(boost::beast::http::verb::get)(
1000             [&app](const crow::Request& req,
1001                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1002                    const std::string& id) {
1003                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1004                 {
1005                     return;
1006                 }
1007                 sdbusplus::asio::getAllProperties(
1008                     *crow::connections::systemBus, telemetry::service,
1009                     telemetry::getDbusTriggerPath(id),
1010                     telemetry::triggerInterface,
1011                     [asyncResp,
1012                      id](const boost::system::error_code& ec,
1013                          const dbus::utility::DBusPropertiesMap& ret) {
1014                         if (ec.value() == EBADR ||
1015                             ec == boost::system::errc::host_unreachable)
1016                         {
1017                             messages::resourceNotFound(asyncResp->res,
1018                                                        "Triggers", id);
1019                             return;
1020                         }
1021                         if (ec)
1022                         {
1023                             BMCWEB_LOG_ERROR("respHandler DBus error {}", ec);
1024                             messages::internalError(asyncResp->res);
1025                             return;
1026                         }
1027 
1028                         if (!telemetry::fillTrigger(asyncResp->res.jsonValue,
1029                                                     id, ret))
1030                         {
1031                             messages::internalError(asyncResp->res);
1032                         }
1033                     });
1034             });
1035 
1036     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/")
1037         .privileges(redfish::privileges::deleteTriggers)
1038         .methods(boost::beast::http::verb::delete_)(
1039             [&app](const crow::Request& req,
1040                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1041                    const std::string& id) {
1042                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1043                 {
1044                     return;
1045                 }
1046                 const std::string triggerPath =
1047                     telemetry::getDbusTriggerPath(id);
1048 
1049                 crow::connections::systemBus->async_method_call(
1050                     [asyncResp, id](const boost::system::error_code& ec) {
1051                         if (ec.value() == EBADR)
1052                         {
1053                             messages::resourceNotFound(asyncResp->res,
1054                                                        "Triggers", id);
1055                             return;
1056                         }
1057 
1058                         if (ec)
1059                         {
1060                             BMCWEB_LOG_ERROR("respHandler DBus error {}", ec);
1061                             messages::internalError(asyncResp->res);
1062                             return;
1063                         }
1064 
1065                         asyncResp->res.result(
1066                             boost::beast::http::status::no_content);
1067                     },
1068                     telemetry::service, triggerPath,
1069                     "xyz.openbmc_project.Object.Delete", "Delete");
1070             });
1071 }
1072 
1073 } // namespace redfish
1074