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