xref: /openbmc/bmcweb/redfish-core/lib/trigger.hpp (revision 1516c21b)
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 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(
326     crow::Response& res, NumericThresholds& numericThresholds, Context& ctx)
327 {
328     std::vector<NumericThresholdParams> parsedParams;
329     if (numericThresholds.upperCritical)
330     {
331         if (!parseThreshold(
332                 res, *numericThresholds.upperCritical,
333                 "xyz.openbmc_project.Telemetry.Trigger.Type.UpperCritical",
334                 parsedParams))
335         {
336             return false;
337         }
338     }
339     if (numericThresholds.upperWarning)
340     {
341         if (!parseThreshold(
342                 res, *numericThresholds.upperWarning,
343                 "xyz.openbmc_project.Telemetry.Trigger.Type.UpperWarning",
344                 parsedParams))
345         {
346             return false;
347         }
348     }
349     if (numericThresholds.lowerWarning)
350     {
351         if (!parseThreshold(
352                 res, *numericThresholds.lowerWarning,
353                 "xyz.openbmc_project.Telemetry.Trigger.Type.LowerWarning",
354                 parsedParams))
355         {
356             return false;
357         }
358     }
359     if (numericThresholds.lowerCritical)
360     {
361         if (!parseThreshold(
362                 res, *numericThresholds.lowerCritical,
363                 "xyz.openbmc_project.Telemetry.Trigger.Type.LowerCritical",
364                 parsedParams))
365         {
366             return false;
367         }
368     }
369 
370     ctx.thresholds = std::move(parsedParams);
371     return true;
372 }
373 
374 inline bool parseDiscreteTriggers(
375     crow::Response& res,
376     std::optional<std::vector<nlohmann::json::object_t>>& discreteTriggers,
377     Context& ctx)
378 {
379     std::vector<DiscreteThresholdParams> parsedParams;
380     if (!discreteTriggers)
381     {
382         ctx.thresholds = std::move(parsedParams);
383         return true;
384     }
385 
386     parsedParams.reserve(discreteTriggers->size());
387     for (nlohmann::json::object_t& thresholdInfo : *discreteTriggers)
388     {
389         std::optional<std::string> name = "";
390         std::string value;
391         std::string dwellTimeStr;
392         std::string severity;
393 
394         if (!json_util::readJsonObject(thresholdInfo, res, "Name", name,
395                                        "Value", value, "DwellTime",
396                                        dwellTimeStr, "Severity", severity))
397         {
398             return false;
399         }
400 
401         std::optional<std::chrono::milliseconds> dwellTime =
402             time_utils::fromDurationString(dwellTimeStr);
403         if (!dwellTime)
404         {
405             messages::propertyValueIncorrect(res, "DwellTime", dwellTimeStr);
406             return false;
407         }
408 
409         std::string dbusSeverity = toDbusSeverity(severity);
410         if (dbusSeverity.empty())
411         {
412             messages::propertyValueIncorrect(res, "Severity", severity);
413             return false;
414         }
415 
416         parsedParams.emplace_back(*name, dbusSeverity,
417                                   static_cast<uint64_t>(dwellTime->count()),
418                                   value);
419     }
420 
421     ctx.thresholds = std::move(parsedParams);
422     return true;
423 }
424 
425 inline bool parseTriggerThresholds(
426     crow::Response& res,
427     std::optional<std::vector<nlohmann::json::object_t>>& discreteTriggers,
428     NumericThresholds& numericThresholds, Context& ctx)
429 {
430     if (discreteTriggers && numericThresholds.any())
431     {
432         messages::propertyValueConflict(res, "DiscreteTriggers",
433                                         "NumericThresholds");
434         messages::propertyValueConflict(res, "NumericThresholds",
435                                         "DiscreteTriggers");
436         return false;
437     }
438 
439     if (ctx.discreteCondition)
440     {
441         if (numericThresholds.any())
442         {
443             messages::propertyValueConflict(res, "DiscreteTriggerCondition",
444                                             "NumericThresholds");
445             messages::propertyValueConflict(res, "NumericThresholds",
446                                             "DiscreteTriggerCondition");
447             return false;
448         }
449     }
450 
451     if (ctx.metricType)
452     {
453         if (*ctx.metricType == MetricType::Discrete && numericThresholds.any())
454         {
455             messages::propertyValueConflict(res, "NumericThresholds",
456                                             "MetricType");
457             return false;
458         }
459         if (*ctx.metricType == MetricType::Numeric && discreteTriggers)
460         {
461             messages::propertyValueConflict(res, "DiscreteTriggers",
462                                             "MetricType");
463             return false;
464         }
465         if (*ctx.metricType == MetricType::Numeric && ctx.discreteCondition)
466         {
467             messages::propertyValueConflict(res, "DiscreteTriggers",
468                                             "DiscreteTriggerCondition");
469             return false;
470         }
471     }
472 
473     if (discreteTriggers || ctx.discreteCondition ||
474         (ctx.metricType && *ctx.metricType == MetricType::Discrete))
475     {
476         if (ctx.discreteCondition)
477         {
478             if (*ctx.discreteCondition == DiscreteCondition::Specified &&
479                 !discreteTriggers)
480             {
481                 messages::createFailedMissingReqProperties(res,
482                                                            "DiscreteTriggers");
483                 return false;
484             }
485             if (discreteTriggers &&
486                 ((*ctx.discreteCondition == DiscreteCondition::Specified &&
487                   discreteTriggers->empty()) ||
488                  (*ctx.discreteCondition == DiscreteCondition::Changed &&
489                   !discreteTriggers->empty())))
490             {
491                 messages::propertyValueConflict(res, "DiscreteTriggers",
492                                                 "DiscreteTriggerCondition");
493                 return false;
494             }
495         }
496         if (!parseDiscreteTriggers(res, discreteTriggers, ctx))
497         {
498             return false;
499         }
500     }
501     else if (numericThresholds.any())
502     {
503         if (!parseNumericThresholds(res, numericThresholds, ctx))
504         {
505             return false;
506         }
507     }
508     else
509     {
510         messages::createFailedMissingReqProperties(
511             res, "'DiscreteTriggers', 'NumericThresholds', "
512                  "'DiscreteTriggerCondition' or 'MetricType'");
513         return false;
514     }
515     return true;
516 }
517 
518 inline bool parseLinks(crow::Response& res,
519                        const std::vector<std::string>& metricReportDefinitions,
520                        Context& ctx)
521 {
522     ctx.reports.reserve(metricReportDefinitions.size());
523     for (const std::string& reportDefinionUri : metricReportDefinitions)
524     {
525         std::optional<sdbusplus::message::object_path> reportPath =
526             getReportPathFromReportDefinitionUri(reportDefinionUri);
527         if (!reportPath)
528         {
529             messages::propertyValueIncorrect(res, "MetricReportDefinitions",
530                                              reportDefinionUri);
531             return false;
532         }
533         ctx.reports.emplace_back(*reportPath);
534     }
535     return true;
536 }
537 
538 inline bool parseMetricProperties(crow::Response& res, Context& ctx)
539 {
540     if (!ctx.metricProperties)
541     {
542         return true;
543     }
544 
545     ctx.sensors.reserve(ctx.metricProperties->size());
546 
547     size_t uriIdx = 0;
548     for (const std::string& uriStr : *ctx.metricProperties)
549     {
550         boost::system::result<boost::urls::url> uri =
551             boost::urls::parse_relative_ref(uriStr);
552         if (!uri)
553         {
554             messages::propertyValueIncorrect(
555                 res, "MetricProperties/" + std::to_string(uriIdx), uriStr);
556             return false;
557         }
558         std::string chassisName;
559         std::string sensorName;
560         if (!crow::utility::readUrlSegments(*uri, "redfish", "v1", "Chassis",
561                                             std::ref(chassisName), "Sensors",
562                                             std::ref(sensorName)))
563         {
564             messages::propertyValueIncorrect(
565                 res, "MetricProperties/" + std::to_string(uriIdx), uriStr);
566             return false;
567         }
568 
569         std::pair<std::string, std::string> split =
570             redfish::sensor_utils::splitSensorNameAndType(sensorName);
571         if (split.first.empty() || split.second.empty())
572         {
573             messages::propertyValueIncorrect(
574                 res, "MetricProperties/" + std::to_string(uriIdx), uriStr);
575             return false;
576         }
577 
578         std::string sensorPath =
579             "/xyz/openbmc_project/sensors/" + split.first + '/' + split.second;
580 
581         ctx.sensors.emplace_back(sensorPath, uriStr);
582         uriIdx++;
583     }
584     return true;
585 }
586 
587 inline bool parsePostTriggerParams(crow::Response& res,
588                                    const crow::Request& req, Context& ctx)
589 {
590     std::optional<std::string> id = "";
591     std::optional<std::string> name = "";
592     std::optional<std::string> metricType;
593     std::optional<std::vector<std::string>> triggerActions;
594     std::optional<std::string> discreteTriggerCondition;
595     std::optional<std::vector<nlohmann::json::object_t>> discreteTriggers;
596     std::optional<std::vector<std::string>> metricReportDefinitions;
597     NumericThresholds thresholds;
598     // clang-format off
599     if (!json_util::readJsonPatch(
600             req, res,
601 	    "Id", id,
602             "Name", name,
603 	    "MetricType", metricType,
604             "TriggerActions", triggerActions,
605             "DiscreteTriggerCondition", discreteTriggerCondition,
606             "DiscreteTriggers", discreteTriggers,
607             "NumericThresholds/UpperCritical", thresholds.upperCritical,
608             "NumericThresholds/UpperWarning", thresholds.upperWarning,
609             "NumericThresholds/LowerWarning", thresholds.lowerWarning,
610             "NumericThresholds/LowerCritical", thresholds.lowerCritical,
611             "MetricProperties", ctx.metricProperties,
612             "Links/MetricReportDefinitions", metricReportDefinitions)
613     )
614     {
615         return false;
616     }
617     // clang-format on
618 
619     ctx.id = *id;
620     ctx.name = *name;
621 
622     if (metricType)
623     {
624         ctx.metricType = getMetricType(*metricType);
625         if (!ctx.metricType)
626         {
627             messages::propertyValueIncorrect(res, "MetricType", *metricType);
628             return false;
629         }
630     }
631 
632     if (discreteTriggerCondition)
633     {
634         ctx.discreteCondition = getDiscreteCondition(*discreteTriggerCondition);
635         if (!ctx.discreteCondition)
636         {
637             messages::propertyValueIncorrect(res, "DiscreteTriggerCondition",
638                                              *discreteTriggerCondition);
639             return false;
640         }
641     }
642 
643     if (triggerActions)
644     {
645         ctx.actions.reserve(triggerActions->size());
646         for (const std::string& action : *triggerActions)
647         {
648             std::string dbusAction = toDbusTriggerAction(action);
649 
650             if (dbusAction.empty())
651             {
652                 messages::propertyValueNotInList(res, action, "TriggerActions");
653                 return false;
654             }
655 
656             ctx.actions.emplace_back(dbusAction);
657         }
658     }
659     if (!parseMetricProperties(res, ctx))
660     {
661         return false;
662     }
663 
664     if (!parseTriggerThresholds(res, discreteTriggers, thresholds, ctx))
665     {
666         return false;
667     }
668 
669     if (metricReportDefinitions)
670     {
671         if (!parseLinks(res, *metricReportDefinitions, ctx))
672         {
673             return false;
674         }
675     }
676     return true;
677 }
678 
679 inline void afterCreateTrigger(
680     const boost::system::error_code& ec, const std::string& dbusPath,
681     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
682 {
683     if (ec == boost::system::errc::file_exists)
684     {
685         messages::resourceAlreadyExists(asyncResp->res, "Trigger", "Id", id);
686         return;
687     }
688     if (ec == boost::system::errc::too_many_files_open)
689     {
690         messages::createLimitReachedForResource(asyncResp->res);
691         return;
692     }
693     if (ec)
694     {
695         messages::internalError(asyncResp->res);
696         BMCWEB_LOG_ERROR("respHandler DBus error {}", ec);
697         return;
698     }
699 
700     const std::optional<std::string>& triggerId =
701         getTriggerIdFromDbusPath(dbusPath);
702     if (!triggerId)
703     {
704         messages::internalError(asyncResp->res);
705         BMCWEB_LOG_ERROR("Unknown data returned by "
706                          "AddTrigger DBus method");
707         return;
708     }
709 
710     messages::created(asyncResp->res);
711     boost::urls::url locationUrl = boost::urls::format(
712         "/redfish/v1/TelemetryService/Triggers/{}", *triggerId);
713     asyncResp->res.addHeader("Location", locationUrl.buffer());
714 }
715 
716 inline std::optional<nlohmann::json::array_t>
717     getTriggerActions(const std::vector<std::string>& dbusActions)
718 {
719     nlohmann::json::array_t triggerActions;
720     for (const std::string& dbusAction : dbusActions)
721     {
722         triggers::TriggerActionEnum redfishAction =
723             toRedfishTriggerAction(dbusAction);
724 
725         if (redfishAction == triggers::TriggerActionEnum::Invalid)
726         {
727             return std::nullopt;
728         }
729 
730         triggerActions.emplace_back(redfishAction);
731     }
732 
733     return triggerActions;
734 }
735 
736 inline std::optional<nlohmann::json::array_t>
737     getDiscreteTriggers(const TriggerThresholdParamsExt& thresholdParams)
738 {
739     nlohmann::json::array_t triggers;
740     const std::vector<DiscreteThresholdParams>* discreteParams =
741         std::get_if<std::vector<DiscreteThresholdParams>>(&thresholdParams);
742 
743     if (discreteParams == nullptr)
744     {
745         return std::nullopt;
746     }
747 
748     for (const auto& [name, severity, dwellTime, value] : *discreteParams)
749     {
750         std::optional<std::string> duration =
751             time_utils::toDurationStringFromUint(dwellTime);
752 
753         if (!duration)
754         {
755             return std::nullopt;
756         }
757         nlohmann::json::object_t trigger;
758         trigger["Name"] = name;
759         trigger["Severity"] = toRedfishSeverity(severity);
760         trigger["DwellTime"] = *duration;
761         trigger["Value"] = value;
762         triggers.emplace_back(std::move(trigger));
763     }
764 
765     return triggers;
766 }
767 
768 inline std::optional<nlohmann::json>
769     getNumericThresholds(const TriggerThresholdParamsExt& thresholdParams)
770 {
771     nlohmann::json::object_t thresholds;
772     const std::vector<NumericThresholdParams>* numericParams =
773         std::get_if<std::vector<NumericThresholdParams>>(&thresholdParams);
774 
775     if (numericParams == nullptr)
776     {
777         return std::nullopt;
778     }
779 
780     for (const auto& [type, dwellTime, activation, reading] : *numericParams)
781     {
782         std::optional<std::string> duration =
783             time_utils::toDurationStringFromUint(dwellTime);
784 
785         if (!duration)
786         {
787             return std::nullopt;
788         }
789         nlohmann::json& threshold = thresholds[toRedfishThresholdName(type)];
790         threshold["Reading"] = reading;
791         threshold["Activation"] = toRedfishActivation(activation);
792         threshold["DwellTime"] = *duration;
793     }
794 
795     return thresholds;
796 }
797 
798 inline std::optional<nlohmann::json> getMetricReportDefinitions(
799     const std::vector<sdbusplus::message::object_path>& reportPaths)
800 {
801     nlohmann::json reports = nlohmann::json::array();
802 
803     for (const sdbusplus::message::object_path& path : reportPaths)
804     {
805         std::string reportId = path.filename();
806         if (reportId.empty())
807         {
808             {
809                 BMCWEB_LOG_ERROR("Property Reports contains invalid value: {}",
810                                  path.str);
811                 return std::nullopt;
812             }
813         }
814 
815         nlohmann::json::object_t report;
816         report["@odata.id"] = boost::urls::format(
817             "/redfish/v1/TelemetryService/MetricReportDefinitions/{}",
818             reportId);
819         reports.emplace_back(std::move(report));
820     }
821 
822     return {std::move(reports)};
823 }
824 
825 inline std::vector<std::string>
826     getMetricProperties(const TriggerSensorsParams& sensors)
827 {
828     std::vector<std::string> metricProperties;
829     metricProperties.reserve(sensors.size());
830     for (const auto& [_, metadata] : sensors)
831     {
832         metricProperties.emplace_back(metadata);
833     }
834 
835     return metricProperties;
836 }
837 
838 inline bool fillTrigger(
839     nlohmann::json& json, const std::string& id,
840     const std::vector<std::pair<std::string, TriggerGetParamsVariant>>&
841         properties)
842 {
843     const std::string* name = nullptr;
844     const bool* discrete = nullptr;
845     const TriggerSensorsParams* sensors = nullptr;
846     const std::vector<sdbusplus::message::object_path>* reports = nullptr;
847     const std::vector<std::string>* triggerActions = nullptr;
848     const TriggerThresholdParamsExt* thresholds = nullptr;
849 
850     const bool success = sdbusplus::unpackPropertiesNoThrow(
851         dbus_utils::UnpackErrorPrinter(), properties, "Name", name, "Discrete",
852         discrete, "Sensors", sensors, "Reports", reports, "TriggerActions",
853         triggerActions, "Thresholds", thresholds);
854 
855     if (!success)
856     {
857         return false;
858     }
859 
860     if (triggerActions != nullptr)
861     {
862         std::optional<nlohmann::json::array_t> redfishTriggerActions =
863             getTriggerActions(*triggerActions);
864         if (!redfishTriggerActions)
865         {
866             BMCWEB_LOG_ERROR(
867                 "Property TriggerActions is invalid in Trigger: {}", id);
868             return false;
869         }
870         json["TriggerActions"] = *redfishTriggerActions;
871     }
872 
873     if (reports != nullptr)
874     {
875         std::optional<nlohmann::json> linkedReports =
876             getMetricReportDefinitions(*reports);
877         if (!linkedReports)
878         {
879             BMCWEB_LOG_ERROR("Property Reports is invalid in Trigger: {}", id);
880             return false;
881         }
882         json["Links"]["MetricReportDefinitions"] = *linkedReports;
883     }
884 
885     if (discrete != nullptr)
886     {
887         if (*discrete)
888         {
889             std::optional<nlohmann::json::array_t> discreteTriggers =
890                 getDiscreteTriggers(*thresholds);
891 
892             if (!discreteTriggers)
893             {
894                 BMCWEB_LOG_ERROR("Property Thresholds is invalid for discrete "
895                                  "triggers in Trigger: {}",
896                                  id);
897                 return false;
898             }
899 
900             json["DiscreteTriggers"] = *discreteTriggers;
901             json["DiscreteTriggerCondition"] =
902                 discreteTriggers->empty() ? "Changed" : "Specified";
903             json["MetricType"] = metric_definition::MetricType::Discrete;
904         }
905         else
906         {
907             std::optional<nlohmann::json> numericThresholds =
908                 getNumericThresholds(*thresholds);
909 
910             if (!numericThresholds)
911             {
912                 BMCWEB_LOG_ERROR("Property Thresholds is invalid for numeric "
913                                  "thresholds in Trigger: {}",
914                                  id);
915                 return false;
916             }
917 
918             json["NumericThresholds"] = *numericThresholds;
919             json["MetricType"] = metric_definition::MetricType::Numeric;
920         }
921     }
922 
923     if (name != nullptr)
924     {
925         json["Name"] = *name;
926     }
927 
928     if (sensors != nullptr)
929     {
930         json["MetricProperties"] = getMetricProperties(*sensors);
931     }
932 
933     json["@odata.type"] = "#Triggers.v1_2_0.Triggers";
934     json["@odata.id"] =
935         boost::urls::format("/redfish/v1/TelemetryService/Triggers/{}", id);
936     json["Id"] = id;
937 
938     return true;
939 }
940 
941 inline void handleTriggerCollectionPost(
942     App& app, const crow::Request& req,
943     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
944 {
945     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
946     {
947         return;
948     }
949 
950     telemetry::Context ctx;
951     if (!telemetry::parsePostTriggerParams(asyncResp->res, req, ctx))
952     {
953         return;
954     }
955 
956     crow::connections::systemBus->async_method_call(
957         [asyncResp, id = ctx.id](const boost::system::error_code& ec,
958                                  const std::string& dbusPath) {
959             afterCreateTrigger(ec, dbusPath, asyncResp, id);
960         },
961         service, "/xyz/openbmc_project/Telemetry/Triggers",
962         "xyz.openbmc_project.Telemetry.TriggerManager", "AddTrigger",
963         "TelemetryService/" + ctx.id, ctx.name, ctx.actions, ctx.sensors,
964         ctx.reports, ctx.thresholds);
965 }
966 
967 } // namespace telemetry
968 
969 inline void requestRoutesTriggerCollection(App& app)
970 {
971     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/")
972         .privileges(redfish::privileges::getTriggersCollection)
973         .methods(boost::beast::http::verb::get)(
974             [&app](const crow::Request& req,
975                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
976                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
977                 {
978                     return;
979                 }
980                 asyncResp->res.jsonValue["@odata.type"] =
981                     "#TriggersCollection.TriggersCollection";
982                 asyncResp->res.jsonValue["@odata.id"] =
983                     "/redfish/v1/TelemetryService/Triggers";
984                 asyncResp->res.jsonValue["Name"] = "Triggers Collection";
985                 constexpr std::array<std::string_view, 1> interfaces{
986                     telemetry::triggerInterface};
987                 collection_util::getCollectionMembers(
988                     asyncResp,
989                     boost::urls::url("/redfish/v1/TelemetryService/Triggers"),
990                     interfaces,
991                     "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService");
992             });
993 
994     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/")
995         .privileges(redfish::privileges::postTriggersCollection)
996         .methods(boost::beast::http::verb::post)(std::bind_front(
997             telemetry::handleTriggerCollectionPost, std::ref(app)));
998 }
999 
1000 inline void requestRoutesTrigger(App& app)
1001 {
1002     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/")
1003         .privileges(redfish::privileges::getTriggers)
1004         .methods(boost::beast::http::verb::get)(
1005             [&app](const crow::Request& req,
1006                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1007                    const std::string& id) {
1008                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1009                 {
1010                     return;
1011                 }
1012                 sdbusplus::asio::getAllProperties(
1013                     *crow::connections::systemBus, telemetry::service,
1014                     telemetry::getDbusTriggerPath(id),
1015                     telemetry::triggerInterface,
1016                     [asyncResp,
1017                      id](const boost::system::error_code& ec,
1018                          const std::vector<std::pair<
1019                              std::string, telemetry::TriggerGetParamsVariant>>&
1020                              ret) {
1021                         if (ec.value() == EBADR ||
1022                             ec == boost::system::errc::host_unreachable)
1023                         {
1024                             messages::resourceNotFound(asyncResp->res,
1025                                                        "Triggers", id);
1026                             return;
1027                         }
1028                         if (ec)
1029                         {
1030                             BMCWEB_LOG_ERROR("respHandler DBus error {}", ec);
1031                             messages::internalError(asyncResp->res);
1032                             return;
1033                         }
1034 
1035                         if (!telemetry::fillTrigger(asyncResp->res.jsonValue,
1036                                                     id, ret))
1037                         {
1038                             messages::internalError(asyncResp->res);
1039                         }
1040                     });
1041             });
1042 
1043     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/")
1044         .privileges(redfish::privileges::deleteTriggers)
1045         .methods(boost::beast::http::verb::delete_)(
1046             [&app](const crow::Request& req,
1047                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1048                    const std::string& id) {
1049                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1050                 {
1051                     return;
1052                 }
1053                 const std::string triggerPath =
1054                     telemetry::getDbusTriggerPath(id);
1055 
1056                 crow::connections::systemBus->async_method_call(
1057                     [asyncResp, id](const boost::system::error_code& ec) {
1058                         if (ec.value() == EBADR)
1059                         {
1060                             messages::resourceNotFound(asyncResp->res,
1061                                                        "Triggers", id);
1062                             return;
1063                         }
1064 
1065                         if (ec)
1066                         {
1067                             BMCWEB_LOG_ERROR("respHandler DBus error {}", ec);
1068                             messages::internalError(asyncResp->res);
1069                             return;
1070                         }
1071 
1072                         asyncResp->res.result(
1073                             boost::beast::http::status::no_content);
1074                     },
1075                     telemetry::service, triggerPath,
1076                     "xyz.openbmc_project.Object.Delete", "Delete");
1077             });
1078 }
1079 
1080 } // namespace redfish
1081