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