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