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