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