1 #include "dbus_environment.hpp"
2 #include "helpers.hpp"
3 #include "mocks/json_storage_mock.hpp"
4 #include "mocks/trigger_factory_mock.hpp"
5 #include "mocks/trigger_mock.hpp"
6 #include "params/trigger_params.hpp"
7 #include "trigger.hpp"
8 #include "trigger_manager.hpp"
9 #include "utils/conversion_trigger.hpp"
10 #include "utils/dbus_path_utils.hpp"
11 #include "utils/string_utils.hpp"
12 #include "utils/transform.hpp"
13 
14 using namespace testing;
15 using sdbusplus::message::object_path;
16 using namespace std::literals::string_literals;
17 
18 class TestTriggerManager : public Test
19 {
20   public:
21     TriggerParams triggerParams;
22     std::pair<boost::system::error_code, std::string>
23         addTrigger(const TriggerParams& params)
24     {
25         const auto sensorInfos =
26             utils::fromLabeledSensorsInfo(params.sensors());
27 
28         std::promise<std::pair<boost::system::error_code, std::string>>
29             addTriggerPromise;
30         DbusEnvironment::getBus()->async_method_call(
31             [&addTriggerPromise](boost::system::error_code ec,
32                                  const std::string& path) {
33             addTriggerPromise.set_value({ec, path});
34         },
35             DbusEnvironment::serviceName(), TriggerManager::triggerManagerPath,
36             TriggerManager::triggerManagerIfaceName, "AddTrigger", params.id(),
37             params.name(),
38             utils::transform(
39                 params.triggerActions(),
40                 [](const auto& action) { return actionToString(action); }),
41             sensorInfos, params.reports(),
42             std::visit(utils::FromLabeledThresholdParamConversion(),
43                        params.thresholdParams()));
44         return DbusEnvironment::waitForFuture(addTriggerPromise.get_future());
45     }
46 
47     std::unique_ptr<TriggerManager> makeTriggerManager()
48     {
49         return std::make_unique<TriggerManager>(
50             std::move(triggerFactoryMockPtr), std::move(storageMockPtr),
51             DbusEnvironment::getObjServer());
52     }
53 
54     void SetUp() override
55     {
56         sut = makeTriggerManager();
57     }
58 
59     std::unique_ptr<StorageMock> storageMockPtr =
60         std::make_unique<NiceMock<StorageMock>>();
61     StorageMock& storageMock = *storageMockPtr;
62     std::unique_ptr<TriggerFactoryMock> triggerFactoryMockPtr =
63         std::make_unique<NiceMock<TriggerFactoryMock>>();
64     TriggerFactoryMock& triggerFactoryMock = *triggerFactoryMockPtr;
65     std::unique_ptr<TriggerMock> triggerMockPtr =
66         std::make_unique<NiceMock<TriggerMock>>(TriggerParams().id());
67     TriggerMock& triggerMock = *triggerMockPtr;
68     std::unique_ptr<TriggerManager> sut;
69     MockFunction<void(std::string)> checkPoint;
70 };
71 
72 TEST_F(TestTriggerManager, addTrigger)
73 {
74     triggerFactoryMock.expectMake(TriggerParams(), Ref(*sut), Ref(storageMock))
75         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
76 
77     auto [ec, path] = addTrigger(TriggerParams());
78     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
79     EXPECT_THAT(path, Eq(triggerMock.getPath()));
80 }
81 
82 TEST_F(TestTriggerManager, addTriggerWithDiscreteThresholds)
83 {
84     TriggerParams triggerParamsDiscrete;
85     auto thresholds = std::vector<discrete::LabeledThresholdParam>{
86         {"discrete_threshold1", discrete::Severity::ok, 10, "11.0"},
87         {"discrete_threshold2", discrete::Severity::warning, 10, "12.0"},
88         {"discrete_threshold3", discrete::Severity::critical, 10, "13.0"}};
89 
90     triggerParamsDiscrete.thresholdParams(thresholds);
91 
92     auto [ec, path] = addTrigger(triggerParamsDiscrete);
93     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
94     EXPECT_THAT(path, Eq(triggerMock.getPath()));
95 }
96 
97 TEST_F(TestTriggerManager, addDiscreteTriggerWithoutThresholds)
98 {
99     TriggerParams triggerParamsDiscrete;
100     auto thresholds = std::vector<discrete::LabeledThresholdParam>();
101 
102     triggerParamsDiscrete.thresholdParams(thresholds);
103 
104     auto [ec, path] = addTrigger(triggerParamsDiscrete);
105     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
106     EXPECT_THAT(path, Eq(triggerMock.getPath()));
107 }
108 
109 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerTwice)
110 {
111     triggerFactoryMock.expectMake(TriggerParams(), Ref(*sut), Ref(storageMock))
112         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
113 
114     addTrigger(TriggerParams());
115 
116     auto [ec, path] = addTrigger(TriggerParams());
117     EXPECT_THAT(ec.value(), Eq(boost::system::errc::file_exists));
118     EXPECT_THAT(path, Eq(std::string()));
119 }
120 
121 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithInvalidId)
122 {
123     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
124         .Times(0);
125 
126     auto [ec, path] = addTrigger(TriggerParams().id("not valid?"));
127     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
128     EXPECT_THAT(path, Eq(std::string()));
129 }
130 
131 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithDuplicatesInReportsIds)
132 {
133     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
134         .Times(0);
135 
136     auto [ec, path] = addTrigger(
137         TriggerParams().reportIds({"trigger1", "trigger2", "trigger1"}));
138     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
139     EXPECT_THAT(path, Eq(std::string()));
140 }
141 
142 TEST_F(TestTriggerManager, addTriggerWithProperReportPaths)
143 {
144     auto [ec, path] = addTrigger(TriggerParams().reports(
145         {object_path("/xyz/openbmc_project/Telemetry/Reports/MyReport"),
146          object_path(
147              "/xyz/openbmc_project/Telemetry/Reports/MyPrefix/MyReport")}));
148     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
149     EXPECT_THAT(path, Eq(triggerMock.getPath()));
150 }
151 
152 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithBadReportsPath)
153 {
154     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
155         .Times(0);
156 
157     auto [ec, path] = addTrigger(TriggerParams().reports(
158         {object_path("/xyz/openbmc_project/Telemetry/NotReports/MyReport")}));
159     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
160     EXPECT_THAT(path, Eq(std::string()));
161 }
162 
163 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithTooManyReportPrefixes)
164 {
165     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
166         .Times(0);
167 
168     auto [ec, path] = addTrigger(TriggerParams().reports({object_path(
169         "/xyz/openbmc_project/Telemetry/Reports/P1/P2/MyReport")}));
170     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
171     EXPECT_THAT(path, Eq(std::string()));
172 }
173 
174 TEST_F(TestTriggerManager, addTriggerWithoutIdAndName)
175 {
176     triggerFactoryMock
177         .expectMake(TriggerParams()
178                         .id(TriggerManager::triggerNameDefault)
179                         .name(TriggerManager::triggerNameDefault),
180                     Ref(*sut), Ref(storageMock))
181         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
182 
183     auto [ec, path] = addTrigger(TriggerParams().id("").name(""));
184     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
185     EXPECT_THAT(path, Not(Eq("")));
186 }
187 
188 TEST_F(TestTriggerManager, addTriggerWithPrefixId)
189 {
190     triggerFactoryMock
191         .expectMake(TriggerParams()
192                         .id("TelemetryService/HackyName")
193                         .name("Hacky/Name!@#$"),
194                     Ref(*sut), Ref(storageMock))
195         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
196 
197     auto [ec, path] = addTrigger(
198         TriggerParams().id("TelemetryService/").name("Hacky/Name!@#$"));
199     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
200     EXPECT_THAT(path, Not(Eq("")));
201 }
202 
203 TEST_F(TestTriggerManager, addTriggerWithoutIdTwice)
204 {
205     addTrigger(TriggerParams().id(""));
206 
207     auto [ec, path] = addTrigger(TriggerParams().id(""));
208     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
209     EXPECT_THAT(path, Not(Eq("")));
210 }
211 
212 TEST_F(TestTriggerManager, addTriggerWithoutIdAndWithLongNameTwice)
213 {
214     std::string longName = utils::string_utils::getMaxName();
215     addTrigger(TriggerParams().id("").name(longName));
216 
217     auto [ec, path] = addTrigger(TriggerParams().id("").name(longName));
218     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
219     EXPECT_THAT(path, Not(Eq("")));
220 }
221 
222 TEST_F(TestTriggerManager, addTriggerWithMaxLengthId)
223 {
224     std::string reportId = utils::string_utils::getMaxId();
225     triggerParams.id(reportId);
226     triggerFactoryMock.expectMake(triggerParams, Ref(*sut), Ref(storageMock));
227 
228     auto [ec, path] = addTrigger(triggerParams);
229 
230     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
231     EXPECT_THAT(path, Eq("/"s + reportId));
232 }
233 
234 TEST_F(TestTriggerManager, addTriggerWithMaxLengthPrefix)
235 {
236     std::string reportId = utils::string_utils::getMaxPrefix() + "/MyId";
237     triggerParams.id(reportId);
238     triggerFactoryMock.expectMake(triggerParams, Ref(*sut), Ref(storageMock));
239 
240     auto [ec, path] = addTrigger(triggerParams);
241 
242     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
243     EXPECT_THAT(path, Eq("/"s + reportId));
244 }
245 
246 TEST_F(TestTriggerManager, addTriggerWithMaxLengthName)
247 {
248     triggerParams.name(utils::string_utils::getMaxName());
249     triggerFactoryMock.expectMake(triggerParams, Ref(*sut), Ref(storageMock));
250 
251     auto [ec, path] = addTrigger(triggerParams);
252 
253     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
254     EXPECT_THAT(path, Eq("/"s + triggerParams.id()));
255 }
256 
257 TEST_F(TestTriggerManager, addTriggerWithMaxLengthDiscreteThresholdName)
258 {
259     namespace ts = utils::tstring;
260 
261     triggerParams =
262         TriggerParams()
263             .id("DiscreteTrigger")
264             .name("My Discrete Trigger")
265             .thresholdParams(std::vector<discrete::LabeledThresholdParam>{
266                 discrete::LabeledThresholdParam{
267                     utils::string_utils::getMaxName(),
268                     discrete::Severity::warning, Milliseconds(10).count(),
269                     "15.2"}});
270 
271     triggerFactoryMock.expectMake(triggerParams, Ref(*sut), Ref(storageMock));
272 
273     auto [ec, path] = addTrigger(triggerParams);
274 
275     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
276     EXPECT_THAT(path, Eq("/"s + triggerParams.id()));
277 }
278 
279 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithTooLongFullId)
280 {
281     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
282         .Times(0);
283 
284     triggerParams.id(
285         std::string(utils::constants::maxReportFullIdLength + 1, 'z'));
286 
287     auto [ec, path] = addTrigger(triggerParams);
288 
289     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
290     EXPECT_THAT(path, Eq(std::string()));
291 }
292 
293 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithTooLongId)
294 {
295     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
296         .Times(0);
297 
298     triggerParams.id(utils::string_utils::getTooLongId());
299 
300     auto [ec, path] = addTrigger(triggerParams);
301 
302     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
303     EXPECT_THAT(path, Eq(std::string()));
304 }
305 
306 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithTooLongPrefix)
307 {
308     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
309         .Times(0);
310 
311     triggerParams.id(utils::string_utils::getTooLongPrefix() + "/MyId");
312 
313     auto [ec, path] = addTrigger(triggerParams);
314 
315     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
316     EXPECT_THAT(path, Eq(std::string()));
317 }
318 
319 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithTooManyPrefixes)
320 {
321     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
322         .Times(0);
323 
324     std::string reportId;
325     for (size_t i = 0; i < utils::constants::maxPrefixesInId + 1; i++)
326     {
327         reportId += "prefix/";
328     }
329     reportId += "MyId";
330 
331     triggerParams.id(reportId);
332 
333     auto [ec, path] = addTrigger(triggerParams);
334 
335     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
336     EXPECT_THAT(path, Eq(std::string()));
337 }
338 
339 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithTooLongName)
340 {
341     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
342         .Times(0);
343 
344     triggerParams.name(utils::string_utils::getTooLongName());
345 
346     auto [ec, path] = addTrigger(triggerParams);
347 
348     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
349     EXPECT_THAT(path, Eq(std::string()));
350 }
351 
352 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithTooLongMetricId)
353 {
354     namespace ts = utils::tstring;
355 
356     triggerParams =
357         TriggerParams()
358             .id("DiscreteTrigger")
359             .name("My Discrete Trigger")
360             .thresholdParams(std::vector<discrete::LabeledThresholdParam>{
361                 discrete::LabeledThresholdParam{
362                     utils::string_utils::getTooLongName(),
363                     discrete::Severity::warning, Milliseconds(10).count(),
364                     "15.2"}});
365 
366     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
367         .Times(0);
368 
369     auto [ec, path] = addTrigger(triggerParams);
370 
371     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
372     EXPECT_THAT(path, Eq(std::string()));
373 }
374 
375 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWhenMaxTriggerIsReached)
376 {
377     auto triggerParams = TriggerParams();
378 
379     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
380         .Times(TriggerManager::maxTriggers);
381 
382     for (size_t i = 0; i < TriggerManager::maxTriggers; i++)
383     {
384         triggerParams.id(TriggerParams().id() + std::to_string(i));
385 
386         auto [ec, path] = addTrigger(triggerParams);
387         EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
388     }
389 
390     triggerParams.id(TriggerParams().id() +
391                      std::to_string(TriggerManager::maxTriggers));
392     auto [ec, path] = addTrigger(triggerParams);
393     EXPECT_THAT(ec.value(), Eq(boost::system::errc::too_many_files_open));
394     EXPECT_THAT(path, Eq(std::string()));
395 }
396 
397 TEST_F(TestTriggerManager, removeTrigger)
398 {
399     {
400         InSequence seq;
401         triggerFactoryMock
402             .expectMake(TriggerParams(), Ref(*sut), Ref(storageMock))
403             .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
404         EXPECT_CALL(triggerMock, Die());
405         EXPECT_CALL(checkPoint, Call("end"));
406     }
407 
408     addTrigger(TriggerParams());
409     sut->removeTrigger(&triggerMock);
410     checkPoint.Call("end");
411 }
412 
413 TEST_F(TestTriggerManager, removingTriggerThatIsNotInContainerHasNoEffect)
414 {
415     {
416         InSequence seq;
417         EXPECT_CALL(checkPoint, Call("end"));
418         EXPECT_CALL(triggerMock, Die());
419     }
420 
421     sut->removeTrigger(&triggerMock);
422     checkPoint.Call("end");
423 }
424 
425 TEST_F(TestTriggerManager, removingSameTriggerTwiceHasNoSideEffect)
426 {
427     {
428         InSequence seq;
429         triggerFactoryMock
430             .expectMake(TriggerParams(), Ref(*sut), Ref(storageMock))
431             .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
432         EXPECT_CALL(triggerMock, Die());
433         EXPECT_CALL(checkPoint, Call("end"));
434     }
435 
436     addTrigger(TriggerParams());
437     sut->removeTrigger(&triggerMock);
438     sut->removeTrigger(&triggerMock);
439     checkPoint.Call("end");
440 }
441 class TestTriggerManagerStorage : public TestTriggerManager
442 {
443   public:
444     using FilePath = interfaces::JsonStorage::FilePath;
445     using DirectoryPath = interfaces::JsonStorage::DirectoryPath;
446 
447     void SetUp() override
448     {
449         ON_CALL(storageMock, list())
450             .WillByDefault(Return(std::vector<FilePath>{
451                 {FilePath("trigger1")}, {FilePath("trigger2")}}));
452 
453         ON_CALL(storageMock, load(FilePath("trigger1")))
454             .WillByDefault(InvokeWithoutArgs([this] { return data1; }));
455 
456         data2["Id"] = "Trigger2";
457         data2["Name"] = "Second Trigger";
458         ON_CALL(storageMock, load(FilePath("trigger2")))
459             .WillByDefault(InvokeWithoutArgs([this] { return data2; }));
460     }
461 
462     nlohmann::json data1 = nlohmann::json{
463         {"Version", Trigger::triggerVersion},
464         {"Id", TriggerParams().id()},
465         {"Name", TriggerParams().name()},
466         {"ThresholdParamsDiscriminator",
467          TriggerParams().thresholdParams().index()},
468         {"TriggerActions", utils::transform(TriggerParams().triggerActions(),
469                                             [](const auto& action) {
470         return actionToString(action);
471     })},
472         {"ThresholdParams", utils::labeledThresholdParamsToJson(
473                                 TriggerParams().thresholdParams())},
474         {"ReportIds", TriggerParams().reportIds()},
475         {"Sensors", TriggerParams().sensors()}};
476 
477     nlohmann::json data2 = data1;
478 };
479 
480 TEST_F(TestTriggerManagerStorage, triggerManagerCtorAddTriggerFromStorage)
481 {
482     triggerFactoryMock.expectMake(TriggerParams(), _, Ref(storageMock));
483     triggerFactoryMock.expectMake(
484         TriggerParams().id("Trigger2").name("Second Trigger"), _,
485         Ref(storageMock));
486     EXPECT_CALL(storageMock, remove(_)).Times(0);
487 
488     sut = makeTriggerManager();
489 }
490 
491 TEST_F(TestTriggerManagerStorage,
492        triggerManagerCtorRemoveDiscreteTriggerFromStorage)
493 {
494     LabeledTriggerThresholdParams thresholdParams =
495         std::vector<discrete::LabeledThresholdParam>{
496             {"userId1", discrete::Severity::warning, 15, "10.0"},
497             {"userId2", discrete::Severity::critical, 5, "20.0"}};
498 
499     data1["ThresholdParamsDiscriminator"] = thresholdParams.index();
500 
501     data1["ThresholdParams"] =
502         utils::labeledThresholdParamsToJson(thresholdParams);
503 
504     EXPECT_CALL(storageMock, remove(FilePath("trigger1"))).Times(0);
505 
506     sut = makeTriggerManager();
507 }
508 
509 TEST_F(TestTriggerManagerStorage,
510        triggerManagerCtorRemoveDiscreteTriggerFromStorage2)
511 {
512     data1["IsDiscrete"] = true;
513 
514     EXPECT_CALL(storageMock, remove(FilePath("trigger1"))).Times(0);
515 
516     sut = makeTriggerManager();
517 }
518 
519 TEST_F(TestTriggerManagerStorage,
520        triggerManagerCtorAddProperRemoveInvalidTriggerFromStorage)
521 {
522     data1["Version"] = Trigger::triggerVersion - 1;
523 
524     triggerFactoryMock.expectMake(
525         TriggerParams().id("Trigger2").name("Second Trigger"), _,
526         Ref(storageMock));
527     EXPECT_CALL(storageMock, remove(FilePath("trigger1")));
528 
529     sut = makeTriggerManager();
530 }
531