xref: /openbmc/telemetry/tests/src/test_report.cpp (revision e28aa53d)
1 #include "dbus_environment.hpp"
2 #include "helpers.hpp"
3 #include "mocks/json_storage_mock.hpp"
4 #include "mocks/metric_mock.hpp"
5 #include "mocks/report_manager_mock.hpp"
6 #include "params/report_params.hpp"
7 #include "report.hpp"
8 #include "report_manager.hpp"
9 #include "utils/conv_container.hpp"
10 #include "utils/tstring.hpp"
11 
12 #include <sdbusplus/exception.hpp>
13 
14 using namespace testing;
15 using namespace std::literals::string_literals;
16 using namespace std::chrono_literals;
17 namespace tstring = utils::tstring;
18 
19 class TestReport : public Test
20 {
21   public:
22     ReportParams defaultParams;
23 
24     std::unique_ptr<ReportManagerMock> reportManagerMock =
25         std::make_unique<NiceMock<ReportManagerMock>>();
26     testing::NiceMock<StorageMock> storageMock;
27     std::vector<std::shared_ptr<MetricMock>> metricMocks;
28     std::unique_ptr<Report> sut;
29 
30     MockFunction<void()> checkPoint;
31 
32     void initMetricMocks(
33         const std::vector<LabeledMetricParameters>& metricParameters)
34     {
35         for (auto i = metricMocks.size(); i < metricParameters.size(); ++i)
36         {
37             metricMocks.emplace_back(std::make_shared<NiceMock<MetricMock>>());
38         }
39         metricMocks.resize(metricParameters.size());
40 
41         std::vector<MetricValue> readings{{MetricValue{"a", "b", 17.1, 114},
42                                            MetricValue{"aa", "bb", 42.0, 74}}};
43         readings.resize(metricParameters.size());
44 
45         for (size_t i = 0; i < metricParameters.size(); ++i)
46         {
47             ON_CALL(*metricMocks[i], getReadings())
48                 .WillByDefault(Return(std::vector({readings[i]})));
49             ON_CALL(*metricMocks[i], dumpConfiguration())
50                 .WillByDefault(Return(metricParameters[i]));
51         }
52     }
53 
54     void SetUp() override
55     {
56         sut = makeReport(ReportParams());
57     }
58 
59     static interfaces::JsonStorage::FilePath to_file_path(std::string name)
60     {
61         return interfaces::JsonStorage::FilePath(
62             std::to_string(std::hash<std::string>{}(name)));
63     }
64 
65     std::unique_ptr<Report> makeReport(const ReportParams& params)
66     {
67         initMetricMocks(params.metricParameters());
68 
69         return std::make_unique<Report>(
70             DbusEnvironment::getIoc(), DbusEnvironment::getObjServer(),
71             params.reportName(), params.reportingType(),
72             params.emitReadingUpdate(), params.logToMetricReportCollection(),
73             params.interval(), *reportManagerMock, storageMock,
74             utils::convContainer<std::shared_ptr<interfaces::Metric>>(
75                 metricMocks),
76             params.enabled());
77     }
78 
79     template <class T>
80     static T getProperty(const std::string& path, const std::string& property)
81     {
82         return DbusEnvironment::getProperty<T>(path, Report::reportIfaceName,
83                                                property);
84     }
85 
86     template <class T>
87     static boost::system::error_code setProperty(const std::string& path,
88                                                  const std::string& property,
89                                                  const T& newValue)
90     {
91         return DbusEnvironment::setProperty<T>(path, Report::reportIfaceName,
92                                                property, newValue);
93     }
94 
95     boost::system::error_code call(const std::string& path,
96                                    const std::string& interface,
97                                    const std::string& method)
98     {
99         std::promise<boost::system::error_code> methodPromise;
100         DbusEnvironment::getBus()->async_method_call(
101             [&methodPromise](boost::system::error_code ec) {
102                 methodPromise.set_value(ec);
103             },
104             DbusEnvironment::serviceName(), path, interface, method);
105         return DbusEnvironment::waitForFuture(methodPromise.get_future());
106     }
107 
108     boost::system::error_code update(const std::string& path)
109     {
110         return call(path, Report::reportIfaceName, "Update");
111     }
112 
113     boost::system::error_code deleteReport(const std::string& path)
114     {
115         return call(path, Report::deleteIfaceName, "Delete");
116     }
117 };
118 
119 TEST_F(TestReport, verifyIfPropertiesHaveValidValue)
120 {
121     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Enabled"),
122                 Eq(defaultParams.enabled()));
123     EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "Interval"),
124                 Eq(defaultParams.interval().count()));
125     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistency"), Eq(true));
126     EXPECT_THAT(getProperty<bool>(sut->getPath(), "EmitsReadingsUpdate"),
127                 Eq(defaultParams.emitReadingUpdate()));
128     EXPECT_THAT(
129         getProperty<bool>(sut->getPath(), "LogToMetricReportsCollection"),
130         Eq(defaultParams.logToMetricReportCollection()));
131     EXPECT_THAT(getProperty<ReadingParameters>(
132                     sut->getPath(), "ReadingParametersFutureVersion"),
133                 Eq(toReadingParameters(defaultParams.metricParameters())));
134 }
135 
136 TEST_F(TestReport, readingsAreInitialyEmpty)
137 {
138     EXPECT_THAT(getProperty<Readings>(sut->getPath(), "Readings"),
139                 Eq(Readings{}));
140 }
141 
142 TEST_F(TestReport, setEnabledWithNewValue)
143 {
144     bool newValue = !defaultParams.enabled();
145     EXPECT_THAT(setProperty(sut->getPath(), "Enabled", newValue).value(),
146                 Eq(boost::system::errc::success));
147     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Enabled"), Eq(newValue));
148 }
149 
150 TEST_F(TestReport, setIntervalWithValidValue)
151 {
152     uint64_t newValue = defaultParams.interval().count() + 1;
153     EXPECT_THAT(setProperty(sut->getPath(), "Interval", newValue).value(),
154                 Eq(boost::system::errc::success));
155     EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "Interval"),
156                 Eq(newValue));
157 }
158 
159 TEST_F(TestReport, settingIntervalWithInvalidValueDoesNotChangeProperty)
160 {
161     uint64_t newValue = defaultParams.interval().count() - 1;
162     EXPECT_THAT(setProperty(sut->getPath(), "Interval", newValue).value(),
163                 Eq(boost::system::errc::success));
164     EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "Interval"),
165                 Eq(defaultParams.interval().count()));
166 }
167 
168 TEST_F(TestReport, settingEmitsReadingsUpdateHaveNoEffect)
169 {
170     EXPECT_THAT(setProperty(sut->getPath(), "EmitsReadingsUpdate",
171                             !defaultParams.emitReadingUpdate())
172                     .value(),
173                 Eq(boost::system::errc::read_only_file_system));
174     EXPECT_THAT(getProperty<bool>(sut->getPath(), "EmitsReadingsUpdate"),
175                 Eq(defaultParams.emitReadingUpdate()));
176 }
177 
178 TEST_F(TestReport, settingLogToMetricReportCollectionHaveNoEffect)
179 {
180     EXPECT_THAT(setProperty(sut->getPath(), "LogToMetricReportsCollection",
181                             !defaultParams.logToMetricReportCollection())
182                     .value(),
183                 Eq(boost::system::errc::read_only_file_system));
184     EXPECT_THAT(
185         getProperty<bool>(sut->getPath(), "LogToMetricReportsCollection"),
186         Eq(defaultParams.logToMetricReportCollection()));
187 }
188 
189 TEST_F(TestReport, settingPersistencyToFalseRemovesReportFromStorage)
190 {
191     EXPECT_CALL(storageMock, remove(to_file_path(sut->getName())));
192 
193     bool persistency = false;
194     EXPECT_THAT(setProperty(sut->getPath(), "Persistency", persistency).value(),
195                 Eq(boost::system::errc::success));
196     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistency"),
197                 Eq(persistency));
198 }
199 
200 TEST_F(TestReport, deleteReport)
201 {
202     EXPECT_CALL(*reportManagerMock, removeReport(sut.get()));
203     auto ec = deleteReport(sut->getPath());
204     EXPECT_THAT(ec, Eq(boost::system::errc::success));
205 }
206 
207 TEST_F(TestReport, deletingNonExistingReportReturnInvalidRequestDescriptor)
208 {
209     auto ec = deleteReport(Report::reportDir + "NonExisting"s);
210     EXPECT_THAT(ec.value(), Eq(EBADR));
211 }
212 
213 TEST_F(TestReport, deleteReportExpectThatFileIsRemoveFromStorage)
214 {
215     EXPECT_CALL(storageMock, remove(to_file_path(sut->getName())));
216     auto ec = deleteReport(sut->getPath());
217     EXPECT_THAT(ec, Eq(boost::system::errc::success));
218 }
219 
220 class TestReportStore :
221     public TestReport,
222     public WithParamInterface<std::pair<std::string, nlohmann::json>>
223 {
224   public:
225     void SetUp() override
226     {}
227 
228     nlohmann::json storedConfiguration;
229 };
230 
231 INSTANTIATE_TEST_SUITE_P(
232     _, TestReportStore,
233     Values(std::make_pair("Enabled"s, nlohmann::json(ReportParams().enabled())),
234            std::make_pair("Version"s, nlohmann::json(4)),
235            std::make_pair("Name"s, nlohmann::json(ReportParams().reportName())),
236            std::make_pair("ReportingType",
237                           nlohmann::json(ReportParams().reportingType())),
238            std::make_pair("EmitsReadingsUpdate",
239                           nlohmann::json(ReportParams().emitReadingUpdate())),
240            std::make_pair(
241                "LogToMetricReportsCollection",
242                nlohmann::json(ReportParams().logToMetricReportCollection())),
243            std::make_pair("Interval",
244                           nlohmann::json(ReportParams().interval().count())),
245            std::make_pair(
246                "ReadingParameters",
247                nlohmann::json(
248                    {{{tstring::SensorPath::str(),
249                       {{{tstring::Service::str(), "Service"},
250                         {tstring::Path::str(),
251                          "/xyz/openbmc_project/sensors/power/p1"}}}},
252                      {tstring::OperationType::str(), OperationType::single},
253                      {tstring::Id::str(), "MetricId1"},
254                      {tstring::MetricMetadata::str(), "Metadata1"},
255                      {tstring::CollectionTimeScope::str(),
256                       CollectionTimeScope::point},
257                      {tstring::CollectionDuration::str(), 0}},
258                     {{tstring::SensorPath::str(),
259                       {{{tstring::Service::str(), "Service"},
260                         {tstring::Path::str(),
261                          "/xyz/openbmc_project/sensors/power/p2"}}}},
262                      {tstring::OperationType::str(), OperationType::single},
263                      {tstring::Id::str(), "MetricId2"},
264                      {tstring::MetricMetadata::str(), "Metadata2"},
265                      {tstring::CollectionTimeScope::str(),
266                       CollectionTimeScope::point},
267                      {tstring::CollectionDuration::str(), 0}}}))));
268 
269 TEST_P(TestReportStore, settingPersistencyToTrueStoresReport)
270 {
271     sut = makeReport(ReportParams());
272 
273     {
274         InSequence seq;
275         EXPECT_CALL(storageMock, remove(to_file_path(sut->getName())));
276         EXPECT_CALL(checkPoint, Call());
277         EXPECT_CALL(storageMock, store(to_file_path(sut->getName()), _))
278             .WillOnce(SaveArg<1>(&storedConfiguration));
279     }
280 
281     setProperty(sut->getPath(), "Persistency", false);
282     checkPoint.Call();
283     setProperty(sut->getPath(), "Persistency", true);
284 
285     const auto& [key, value] = GetParam();
286 
287     ASSERT_THAT(storedConfiguration.at(key), Eq(value));
288 }
289 
290 TEST_P(TestReportStore, reportIsSavedToStorageAfterCreated)
291 {
292     EXPECT_CALL(storageMock,
293                 store(to_file_path(ReportParams().reportName()), _))
294         .WillOnce(SaveArg<1>(&storedConfiguration));
295 
296     sut = makeReport(ReportParams());
297 
298     const auto& [key, value] = GetParam();
299 
300     ASSERT_THAT(storedConfiguration.at(key), Eq(value));
301 }
302 
303 class TestReportValidNames :
304     public TestReport,
305     public WithParamInterface<ReportParams>
306 {
307   public:
308     void SetUp() override
309     {}
310 };
311 
312 INSTANTIATE_TEST_SUITE_P(
313     ValidNames, TestReportValidNames,
314     Values(ReportParams().reportName("Valid_1"),
315            ReportParams().reportName("Valid_1/Valid_2"),
316            ReportParams().reportName("Valid_1/Valid_2/Valid_3")));
317 
318 TEST_P(TestReportValidNames, reportCtorDoesNotThrowOnValidName)
319 {
320     EXPECT_NO_THROW(makeReport(GetParam()));
321 }
322 
323 class TestReportInvalidNames :
324     public TestReport,
325     public WithParamInterface<ReportParams>
326 {
327   public:
328     void SetUp() override
329     {}
330 };
331 
332 INSTANTIATE_TEST_SUITE_P(InvalidNames, TestReportInvalidNames,
333                          Values(ReportParams().reportName("/"),
334                                 ReportParams().reportName("/Invalid"),
335                                 ReportParams().reportName("Invalid/"),
336                                 ReportParams().reportName("Invalid/Invalid/"),
337                                 ReportParams().reportName("Invalid?")));
338 
339 TEST_P(TestReportInvalidNames, reportCtorThrowOnInvalidName)
340 {
341     EXPECT_THROW(makeReport(GetParam()), sdbusplus::exception::SdBusError);
342 }
343 
344 TEST_F(TestReportInvalidNames, reportCtorThrowOnInvalidNameAndNoStoreIsCalled)
345 {
346     EXPECT_CALL(storageMock, store).Times(0);
347     EXPECT_THROW(makeReport(ReportParams().reportName("/Invalid")),
348                  sdbusplus::exception::SdBusError);
349 }
350 
351 class TestReportAllReportTypes :
352     public TestReport,
353     public WithParamInterface<ReportParams>
354 {
355   public:
356     void SetUp() override
357     {
358         sut = makeReport(GetParam());
359     }
360 };
361 
362 INSTANTIATE_TEST_SUITE_P(_, TestReportAllReportTypes,
363                          Values(ReportParams().reportingType("OnRequest"),
364                                 ReportParams().reportingType("OnChange"),
365                                 ReportParams().reportingType("Periodic")));
366 
367 TEST_P(TestReportAllReportTypes, returnPropertValueOfReportType)
368 {
369     EXPECT_THAT(getProperty<std::string>(sut->getPath(), "ReportingType"),
370                 Eq(GetParam().reportingType()));
371 }
372 
373 TEST_P(TestReportAllReportTypes, updateReadingsCallEnabledPropertyOff)
374 {
375     const uint64_t expectedTime = std::time(0);
376 
377     setProperty(sut->getPath(), "Enabled", false);
378     sut->updateReadings();
379     const auto [timestamp, readings] =
380         getProperty<Readings>(sut->getPath(), "Readings");
381 
382     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Enabled"), Eq(false));
383     EXPECT_THAT(timestamp, Lt(expectedTime));
384 }
385 
386 TEST_P(TestReportAllReportTypes, updateReadingsCallEnabledPropertyOn)
387 {
388     const uint64_t expectedTime = std::time(0);
389 
390     sut->updateReadings();
391     const auto [timestamp, readings] =
392         getProperty<Readings>(sut->getPath(), "Readings");
393 
394     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Enabled"), Eq(true));
395     EXPECT_THAT(timestamp, Ge(expectedTime));
396 }
397 
398 class TestReportOnRequestType : public TestReport
399 {
400     void SetUp() override
401     {
402         sut = makeReport(ReportParams().reportingType("OnRequest"));
403     }
404 };
405 
406 TEST_F(TestReportOnRequestType, updatesReadingTimestamp)
407 {
408     const uint64_t expectedTime = std::time(0);
409 
410     ASSERT_THAT(update(sut->getPath()), Eq(boost::system::errc::success));
411 
412     const auto [timestamp, readings] =
413         getProperty<Readings>(sut->getPath(), "Readings");
414 
415     EXPECT_THAT(timestamp, Ge(expectedTime));
416 }
417 
418 TEST_F(TestReportOnRequestType, updatesReadingWhenUpdateIsCalled)
419 {
420     ASSERT_THAT(update(sut->getPath()), Eq(boost::system::errc::success));
421 
422     const auto [timestamp, readings] =
423         getProperty<Readings>(sut->getPath(), "Readings");
424 
425     EXPECT_THAT(readings,
426                 ElementsAre(std::make_tuple("a"s, "b"s, 17.1, 114u),
427                             std::make_tuple("aa"s, "bb"s, 42.0, 74u)));
428 }
429 
430 class TestReportNonOnRequestType :
431     public TestReport,
432     public WithParamInterface<ReportParams>
433 {
434     void SetUp() override
435     {
436         sut = makeReport(GetParam());
437     }
438 };
439 
440 INSTANTIATE_TEST_SUITE_P(_, TestReportNonOnRequestType,
441                          Values(ReportParams().reportingType("Periodic"),
442                                 ReportParams().reportingType("OnChange")));
443 
444 TEST_P(TestReportNonOnRequestType, readingsAreNotUpdateOnUpdateCall)
445 {
446     ASSERT_THAT(update(sut->getPath()), Eq(boost::system::errc::success));
447 
448     EXPECT_THAT(getProperty<Readings>(sut->getPath(), "Readings"),
449                 Eq(Readings{}));
450 }
451 
452 class TestReportNonPeriodicReport :
453     public TestReport,
454     public WithParamInterface<ReportParams>
455 {
456   public:
457     void SetUp() override
458     {
459         sut = makeReport(GetParam());
460     }
461 };
462 
463 INSTANTIATE_TEST_SUITE_P(_, TestReportNonPeriodicReport,
464                          Values(ReportParams().reportingType("OnRequest"),
465                                 ReportParams().reportingType("OnChange")));
466 
467 TEST_P(TestReportNonPeriodicReport, readingsAreNotUpdatedAfterIntervalExpires)
468 {
469     DbusEnvironment::sleepFor(ReportManager::minInterval + 1ms);
470 
471     EXPECT_THAT(getProperty<Readings>(sut->getPath(), "Readings"),
472                 Eq(Readings{}));
473 }
474 
475 class TestReportPeriodicReport : public TestReport
476 {
477     void SetUp() override
478     {
479         sut = makeReport(ReportParams().reportingType("Periodic"));
480     }
481 };
482 
483 TEST_F(TestReportPeriodicReport, readingTimestampIsUpdatedAfterIntervalExpires)
484 {
485     const uint64_t expectedTime = std::time(0);
486     DbusEnvironment::sleepFor(ReportManager::minInterval + 1ms);
487 
488     const auto [timestamp, readings] =
489         getProperty<Readings>(sut->getPath(), "Readings");
490 
491     EXPECT_THAT(timestamp, Ge(expectedTime));
492 }
493 
494 TEST_F(TestReportPeriodicReport, readingsAreUpdatedAfterIntervalExpires)
495 {
496     DbusEnvironment::sleepFor(ReportManager::minInterval + 1ms);
497 
498     const auto [timestamp, readings] =
499         getProperty<Readings>(sut->getPath(), "Readings");
500 
501     EXPECT_THAT(readings,
502                 ElementsAre(std::make_tuple("a"s, "b"s, 17.1, 114u),
503                             std::make_tuple("aa"s, "bb"s, 42.0, 74u)));
504 }
505 
506 class TestReportInitialization : public TestReport
507 {
508   public:
509     void SetUp() override
510     {}
511 
512     void monitorProc(sdbusplus::message::message& msg)
513     {
514         std::string iface;
515         std::vector<std::pair<std::string, std::variant<Readings>>>
516             changed_properties;
517         std::vector<std::string> invalidated_properties;
518 
519         msg.read(iface, changed_properties, invalidated_properties);
520 
521         if (iface == Report::reportIfaceName)
522         {
523             for (const auto& [name, value] : changed_properties)
524             {
525                 if (name == "Readings")
526                 {
527                     readingsUpdated.Call();
528                 }
529             }
530         }
531     }
532 
533     void makeMonitor()
534     {
535         monitor = std::make_unique<sdbusplus::bus::match::match>(
536             *DbusEnvironment::getBus(),
537             sdbusplus::bus::match::rules::propertiesChanged(
538                 sut->getPath(), Report::reportIfaceName),
539             [this](auto& msg) { monitorProc(msg); });
540     }
541 
542     std::unique_ptr<sdbusplus::bus::match::match> monitor;
543     MockFunction<void()> readingsUpdated;
544 };
545 
546 TEST_F(TestReportInitialization,
547        metricsAreInitializedWhenEnabledReportConstructed)
548 {
549     initMetricMocks(defaultParams.metricParameters());
550     for (auto& metric : metricMocks)
551     {
552         EXPECT_CALL(*metric, initialize()).Times(1);
553     }
554     sut = makeReport(defaultParams.enabled(true));
555 }
556 
557 TEST_F(TestReportInitialization,
558        metricsAreNotInitializedWhenDisabledReportConstructed)
559 {
560     initMetricMocks(defaultParams.metricParameters());
561     for (auto& metric : metricMocks)
562     {
563         EXPECT_CALL(*metric, initialize()).Times(0);
564     }
565     sut = makeReport(defaultParams.enabled(false));
566 }
567 
568 TEST_F(TestReportInitialization,
569        emitReadingsUpdateIsTrueReadingsPropertiesChangedSingalEmits)
570 {
571     EXPECT_CALL(readingsUpdated, Call())
572         .WillOnce(
573             InvokeWithoutArgs(DbusEnvironment::setPromise("readingsUpdated")));
574 
575     const auto elapsed = DbusEnvironment::measureTime([this] {
576         sut = makeReport(
577             defaultParams.reportingType("Periodic").emitReadingUpdate(true));
578         makeMonitor();
579         EXPECT_TRUE(DbusEnvironment::waitForFuture("readingsUpdated"));
580     });
581 
582     EXPECT_THAT(elapsed, AllOf(Ge(defaultParams.interval()),
583                                Lt(defaultParams.interval() * 2)));
584 }
585 
586 TEST_F(TestReportInitialization,
587        emitReadingsUpdateIsFalseReadingsPropertiesChangesSigalDoesNotEmits)
588 {
589     EXPECT_CALL(readingsUpdated, Call()).Times(0);
590 
591     sut = makeReport(
592         defaultParams.reportingType("Periodic").emitReadingUpdate(false));
593     makeMonitor();
594     DbusEnvironment::sleepFor(defaultParams.interval() * 2);
595 }
596