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