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