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