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