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