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