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