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