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