xref: /openbmc/bmcweb/redfish-core/lib/trigger.hpp (revision b5b62cc15edd2f4f2053a629cbcf6340b15e307e)
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::urls::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::urls::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         if (!(ctx.metricType = getMetricType(*metricType)))
610         {
611             messages::propertyValueIncorrect(res, "MetricType", *metricType);
612             return false;
613         }
614     }
615 
616     if (discreteTriggerCondition)
617     {
618         if (!(ctx.discreteCondition =
619                   getDiscreteCondition(*discreteTriggerCondition)))
620         {
621             messages::propertyValueIncorrect(res, "DiscreteTriggerCondition",
622                                              *discreteTriggerCondition);
623             return false;
624         }
625     }
626 
627     if (triggerActions)
628     {
629         ctx.actions.reserve(triggerActions->size());
630         for (const std::string& action : *triggerActions)
631         {
632             std::string dbusAction = toDbusTriggerAction(action);
633 
634             if (dbusAction.empty())
635             {
636                 messages::propertyValueNotInList(res, action, "TriggerActions");
637                 return false;
638             }
639 
640             ctx.actions.emplace_back(dbusAction);
641         }
642     }
643     if (!parseMetricProperties(res, ctx))
644     {
645         return false;
646     }
647 
648     if (!parseTriggerThresholds(res, discreteTriggers, numericThresholds, ctx))
649     {
650         return false;
651     }
652 
653     if (links)
654     {
655         if (!parseLinks(res, *links, ctx))
656         {
657             return false;
658         }
659     }
660     return true;
661 }
662 
663 inline void afterCreateTrigger(
664     const boost::system::error_code& ec, const std::string& dbusPath,
665     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
666 {
667     if (ec == boost::system::errc::file_exists)
668     {
669         messages::resourceAlreadyExists(asyncResp->res, "Trigger", "Id", id);
670         return;
671     }
672     if (ec == boost::system::errc::too_many_files_open)
673     {
674         messages::createLimitReachedForResource(asyncResp->res);
675         return;
676     }
677     if (ec)
678     {
679         messages::internalError(asyncResp->res);
680         BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
681         return;
682     }
683 
684     const std::optional<std::string>& triggerId =
685         getTriggerIdFromDbusPath(dbusPath);
686     if (!triggerId)
687     {
688         messages::internalError(asyncResp->res);
689         BMCWEB_LOG_ERROR << "Unknown data returned by "
690                             "AddTrigger DBus method";
691         return;
692     }
693 
694     messages::created(asyncResp->res);
695     boost::urls::url locationUrl = boost::urls::format(
696         "/redfish/v1/TelemetryService/Triggers/{}", *triggerId);
697     asyncResp->res.addHeader("Location", locationUrl.buffer());
698 }
699 
700 inline std::optional<nlohmann::json::array_t>
701     getTriggerActions(const std::vector<std::string>& dbusActions)
702 {
703     nlohmann::json::array_t triggerActions;
704     for (const std::string& dbusAction : dbusActions)
705     {
706         triggers::TriggerActionEnum redfishAction =
707             toRedfishTriggerAction(dbusAction);
708 
709         if (redfishAction == triggers::TriggerActionEnum::Invalid)
710         {
711             return std::nullopt;
712         }
713 
714         triggerActions.emplace_back(redfishAction);
715     }
716 
717     return triggerActions;
718 }
719 
720 inline std::optional<nlohmann::json::array_t>
721     getDiscreteTriggers(const TriggerThresholdParamsExt& thresholdParams)
722 {
723     nlohmann::json::array_t triggers;
724     const std::vector<DiscreteThresholdParams>* discreteParams =
725         std::get_if<std::vector<DiscreteThresholdParams>>(&thresholdParams);
726 
727     if (discreteParams == nullptr)
728     {
729         return std::nullopt;
730     }
731 
732     for (const auto& [name, severity, dwellTime, value] : *discreteParams)
733     {
734         std::optional<std::string> duration =
735             time_utils::toDurationStringFromUint(dwellTime);
736 
737         if (!duration)
738         {
739             return std::nullopt;
740         }
741         nlohmann::json::object_t trigger;
742         trigger["Name"] = name;
743         trigger["Severity"] = toRedfishSeverity(severity);
744         trigger["DwellTime"] = *duration;
745         trigger["Value"] = value;
746         triggers.emplace_back(std::move(trigger));
747     }
748 
749     return triggers;
750 }
751 
752 inline std::optional<nlohmann::json>
753     getNumericThresholds(const TriggerThresholdParamsExt& thresholdParams)
754 {
755     nlohmann::json::object_t thresholds;
756     const std::vector<NumericThresholdParams>* numericParams =
757         std::get_if<std::vector<NumericThresholdParams>>(&thresholdParams);
758 
759     if (numericParams == nullptr)
760     {
761         return std::nullopt;
762     }
763 
764     for (const auto& [type, dwellTime, activation, reading] : *numericParams)
765     {
766         std::optional<std::string> duration =
767             time_utils::toDurationStringFromUint(dwellTime);
768 
769         if (!duration)
770         {
771             return std::nullopt;
772         }
773         nlohmann::json& threshold = thresholds[toRedfishThresholdName(type)];
774         threshold["Reading"] = reading;
775         threshold["Activation"] = toRedfishActivation(activation);
776         threshold["DwellTime"] = *duration;
777     }
778 
779     return thresholds;
780 }
781 
782 inline std::optional<nlohmann::json> getMetricReportDefinitions(
783     const std::vector<sdbusplus::message::object_path>& reportPaths)
784 {
785     nlohmann::json reports = nlohmann::json::array();
786 
787     for (const sdbusplus::message::object_path& path : reportPaths)
788     {
789         std::string reportId = path.filename();
790         if (reportId.empty())
791         {
792             {
793                 BMCWEB_LOG_ERROR << "Property Reports contains invalid value: "
794                                  << path.str;
795                 return std::nullopt;
796             }
797         }
798 
799         nlohmann::json::object_t report;
800         report["@odata.id"] = boost::urls::format(
801             "/redfish/v1/TelemetryService/MetricReportDefinitions/{}",
802             reportId);
803         reports.emplace_back(std::move(report));
804     }
805 
806     return {std::move(reports)};
807 }
808 
809 inline std::vector<std::string>
810     getMetricProperties(const TriggerSensorsParams& sensors)
811 {
812     std::vector<std::string> metricProperties;
813     metricProperties.reserve(sensors.size());
814     for (const auto& [_, metadata] : sensors)
815     {
816         metricProperties.emplace_back(metadata);
817     }
818 
819     return metricProperties;
820 }
821 
822 inline bool fillTrigger(
823     nlohmann::json& json, const std::string& id,
824     const std::vector<std::pair<std::string, TriggerGetParamsVariant>>&
825         properties)
826 {
827     const std::string* name = nullptr;
828     const bool* discrete = nullptr;
829     const TriggerSensorsParams* sensors = nullptr;
830     const std::vector<sdbusplus::message::object_path>* reports = nullptr;
831     const std::vector<std::string>* triggerActions = nullptr;
832     const TriggerThresholdParamsExt* thresholds = nullptr;
833 
834     const bool success = sdbusplus::unpackPropertiesNoThrow(
835         dbus_utils::UnpackErrorPrinter(), properties, "Name", name, "Discrete",
836         discrete, "Sensors", sensors, "Reports", reports, "TriggerActions",
837         triggerActions, "Thresholds", thresholds);
838 
839     if (!success)
840     {
841         return false;
842     }
843 
844     if (triggerActions != nullptr)
845     {
846         std::optional<nlohmann::json::array_t> redfishTriggerActions =
847             getTriggerActions(*triggerActions);
848         if (!redfishTriggerActions)
849         {
850             BMCWEB_LOG_ERROR
851                 << "Property TriggerActions is invalid in Trigger: " << id;
852             return false;
853         }
854         json["TriggerActions"] = *redfishTriggerActions;
855     }
856 
857     if (reports != nullptr)
858     {
859         std::optional<nlohmann::json> linkedReports =
860             getMetricReportDefinitions(*reports);
861         if (!linkedReports)
862         {
863             BMCWEB_LOG_ERROR << "Property Reports is invalid in Trigger: "
864                              << 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
880                     << "Property Thresholds is invalid for discrete "
881                        "triggers in Trigger: "
882                     << id;
883                 return false;
884             }
885 
886             json["DiscreteTriggers"] = *discreteTriggers;
887             json["DiscreteTriggerCondition"] =
888                 discreteTriggers->empty() ? "Changed" : "Specified";
889             json["MetricType"] = "Discrete";
890         }
891         else
892         {
893             std::optional<nlohmann::json> numericThresholds =
894                 getNumericThresholds(*thresholds);
895 
896             if (!numericThresholds)
897             {
898                 BMCWEB_LOG_ERROR
899                     << "Property Thresholds is invalid for numeric "
900                        "thresholds in Trigger: "
901                     << id;
902                 return false;
903             }
904 
905             json["NumericThresholds"] = *numericThresholds;
906             json["MetricType"] = "Numeric";
907         }
908     }
909 
910     if (name != nullptr)
911     {
912         json["Name"] = *name;
913     }
914 
915     if (sensors != nullptr)
916     {
917         json["MetricProperties"] = getMetricProperties(*sensors);
918     }
919 
920     json["@odata.type"] = "#Triggers.v1_2_0.Triggers";
921     json["@odata.id"] =
922         boost::urls::format("/redfish/v1/TelemetryService/Triggers/{}", id);
923     json["Id"] = id;
924 
925     return true;
926 }
927 
928 inline void handleTriggerCollectionPost(
929     App& app, const crow::Request& req,
930     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
931 {
932     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
933     {
934         return;
935     }
936 
937     telemetry::Context ctx;
938     if (!telemetry::parsePostTriggerParams(asyncResp->res, req, ctx))
939     {
940         return;
941     }
942 
943     crow::connections::systemBus->async_method_call(
944         [asyncResp, id = ctx.id](const boost::system::error_code& ec,
945                                  const std::string& dbusPath) {
946         afterCreateTrigger(ec, dbusPath, asyncResp, id);
947         },
948         service, "/xyz/openbmc_project/Telemetry/Triggers",
949         "xyz.openbmc_project.Telemetry.TriggerManager", "AddTrigger",
950         "TelemetryService/" + ctx.id, ctx.name, ctx.actions, ctx.sensors,
951         ctx.reports, ctx.thresholds);
952 }
953 
954 } // namespace telemetry
955 
956 inline void requestRoutesTriggerCollection(App& app)
957 {
958     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/")
959         .privileges(redfish::privileges::getTriggersCollection)
960         .methods(boost::beast::http::verb::get)(
961             [&app](const crow::Request& req,
962                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
963         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
964         {
965             return;
966         }
967         asyncResp->res.jsonValue["@odata.type"] =
968             "#TriggersCollection.TriggersCollection";
969         asyncResp->res.jsonValue["@odata.id"] =
970             "/redfish/v1/TelemetryService/Triggers";
971         asyncResp->res.jsonValue["Name"] = "Triggers Collection";
972         constexpr std::array<std::string_view, 1> interfaces{
973             telemetry::triggerInterface};
974         collection_util::getCollectionMembers(
975             asyncResp,
976             boost::urls::url("/redfish/v1/TelemetryService/Triggers"),
977             interfaces,
978             "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService");
979         });
980 
981     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/")
982         .privileges(redfish::privileges::postTriggersCollection)
983         .methods(boost::beast::http::verb::post)(std::bind_front(
984             telemetry::handleTriggerCollectionPost, std::ref(app)));
985 }
986 
987 inline void requestRoutesTrigger(App& app)
988 {
989     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/")
990         .privileges(redfish::privileges::getTriggers)
991         .methods(boost::beast::http::verb::get)(
992             [&app](const crow::Request& req,
993                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
994                    const std::string& id) {
995         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
996         {
997             return;
998         }
999         sdbusplus::asio::getAllProperties(
1000             *crow::connections::systemBus, telemetry::service,
1001             telemetry::getDbusTriggerPath(id), telemetry::triggerInterface,
1002             [asyncResp,
1003              id](const boost::system::error_code& ec,
1004                  const std::vector<std::pair<
1005                      std::string, telemetry::TriggerGetParamsVariant>>& ret) {
1006             if (ec.value() == EBADR ||
1007                 ec == boost::system::errc::host_unreachable)
1008             {
1009                 messages::resourceNotFound(asyncResp->res, "Triggers", id);
1010                 return;
1011             }
1012             if (ec)
1013             {
1014                 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
1015                 messages::internalError(asyncResp->res);
1016                 return;
1017             }
1018 
1019             if (!telemetry::fillTrigger(asyncResp->res.jsonValue, id, ret))
1020             {
1021                 messages::internalError(asyncResp->res);
1022             }
1023             });
1024         });
1025 
1026     BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/")
1027         .privileges(redfish::privileges::deleteTriggers)
1028         .methods(boost::beast::http::verb::delete_)(
1029             [&app](const crow::Request& req,
1030                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1031                    const std::string& id) {
1032         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1033         {
1034             return;
1035         }
1036         const std::string triggerPath = telemetry::getDbusTriggerPath(id);
1037 
1038         crow::connections::systemBus->async_method_call(
1039             [asyncResp, id](const boost::system::error_code& ec) {
1040             if (ec.value() == EBADR)
1041             {
1042                 messages::resourceNotFound(asyncResp->res, "Triggers", id);
1043                 return;
1044             }
1045 
1046             if (ec)
1047             {
1048                 BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
1049                 messages::internalError(asyncResp->res);
1050                 return;
1051             }
1052 
1053             asyncResp->res.result(boost::beast::http::status::no_content);
1054             },
1055             telemetry::service, triggerPath,
1056             "xyz.openbmc_project.Object.Delete", "Delete");
1057         });
1058 }
1059 
1060 } // namespace redfish
1061