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