xref: /openbmc/telemetry/tests/src/test_report.cpp (revision e8fc57514575f07565035db07cb522fe21988224)
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::make_shared<NiceMock<MetricMock>>(),
30         std::make_shared<NiceMock<MetricMock>>(),
31         std::make_shared<NiceMock<MetricMock>>()};
32     std::unique_ptr<Report> sut;
33 
34     MockFunction<void()> checkPoint;
35 
36     TestReport()
37     {
38         ON_CALL(*metricMocks[0], getReading())
39             .WillByDefault(ReturnRefOfCopy(MetricValue{"a", "b", 17.1, 114}));
40         ON_CALL(*metricMocks[1], getReading())
41             .WillByDefault(ReturnRefOfCopy(MetricValue{"aa", "bb", 42.0, 74}));
42         ON_CALL(*metricMocks[2], getReading())
43             .WillByDefault(
44                 ReturnRefOfCopy(MetricValue{"aaa", "bbb", 100.7, 21}));
45 
46         for (size_t i = 0; i < metricMocks.size(); ++i)
47         {
48             using namespace std::string_literals;
49 
50             auto id = std::to_string(i);
51 
52             auto sensorParameters =
53                 LabeledSensorParameters("service"s + id, "path"s + id);
54             auto metricParameters = LabeledMetricParameters(
55                 std::move(sensorParameters), utils::toOperationType(i),
56                 "id"s + id, "metadata"s + id);
57 
58             ON_CALL(*metricMocks[i], dumpConfiguration())
59                 .WillByDefault(Return(std::move(metricParameters)));
60         }
61     }
62 
63     void SetUp() override
64     {
65         sut = makeReport(ReportParams());
66     }
67 
68     static interfaces::JsonStorage::FilePath to_file_path(std::string name)
69     {
70         return interfaces::JsonStorage::FilePath(
71             std::to_string(std::hash<std::string>{}(name)));
72     }
73 
74     std::unique_ptr<Report> makeReport(const ReportParams& params)
75     {
76         return std::make_unique<Report>(
77             DbusEnvironment::getIoc(), DbusEnvironment::getObjServer(),
78             params.reportName(), params.reportingType(),
79             params.emitReadingUpdate(), params.logToMetricReportCollection(),
80             params.interval(), params.readingParameters(), *reportManagerMock,
81             storageMock,
82             utils::convContainer<std::shared_ptr<interfaces::Metric>>(
83                 metricMocks));
84     }
85 
86     template <class T>
87     static T getProperty(const std::string& path, const std::string& property)
88     {
89         std::promise<T> propertyPromise;
90         sdbusplus::asio::getProperty<T>(
91             *DbusEnvironment::getBus(), DbusEnvironment::serviceName(), path,
92             Report::reportIfaceName, property,
93             [&propertyPromise](boost::system::error_code) {
94                 utils::setException(propertyPromise, "GetProperty failed");
95             },
96             [&propertyPromise](T t) { propertyPromise.set_value(t); });
97         return DbusEnvironment::waitForFuture(propertyPromise.get_future());
98     }
99 
100     boost::system::error_code call(const std::string& path,
101                                    const std::string& interface,
102                                    const std::string& method)
103     {
104         std::promise<boost::system::error_code> methodPromise;
105         DbusEnvironment::getBus()->async_method_call(
106             [&methodPromise](boost::system::error_code ec) {
107                 methodPromise.set_value(ec);
108             },
109             DbusEnvironment::serviceName(), path, interface, method);
110         return DbusEnvironment::waitForFuture(methodPromise.get_future());
111     }
112 
113     boost::system::error_code update(const std::string& path)
114     {
115         return call(path, Report::reportIfaceName, "Update");
116     }
117 
118     template <class T>
119     static boost::system::error_code setProperty(const std::string& path,
120                                                  const std::string& property,
121                                                  const T& newValue)
122     {
123         std::promise<boost::system::error_code> setPromise;
124         sdbusplus::asio::setProperty(
125             *DbusEnvironment::getBus(), DbusEnvironment::serviceName(), path,
126             Report::reportIfaceName, property, std::move(newValue),
127             [&setPromise](boost::system::error_code ec) {
128                 setPromise.set_value(ec);
129             },
130             [&setPromise]() {
131                 setPromise.set_value(boost::system::error_code{});
132             });
133         return DbusEnvironment::waitForFuture(setPromise.get_future());
134     }
135 
136     boost::system::error_code deleteReport(const std::string& path)
137     {
138         return call(path, Report::deleteIfaceName, "Delete");
139     }
140 };
141 
142 TEST_F(TestReport, verifyIfPropertiesHaveValidValue)
143 {
144     EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "Interval"),
145                 Eq(defaultParams.interval().count()));
146     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistency"), Eq(true));
147     EXPECT_THAT(getProperty<bool>(sut->getPath(), "EmitsReadingsUpdate"),
148                 Eq(defaultParams.emitReadingUpdate()));
149     EXPECT_THAT(
150         getProperty<bool>(sut->getPath(), "LogToMetricReportsCollection"),
151         Eq(defaultParams.logToMetricReportCollection()));
152     EXPECT_THAT(
153         getProperty<ReadingParameters>(sut->getPath(), "ReadingParameters"),
154         Eq(defaultParams.readingParameters()));
155 }
156 
157 TEST_F(TestReport, readingsAreInitialyEmpty)
158 {
159     EXPECT_THAT(getProperty<Readings>(sut->getPath(), "Readings"),
160                 Eq(Readings{}));
161 }
162 
163 TEST_F(TestReport, setIntervalWithValidValue)
164 {
165     uint64_t newValue = defaultParams.interval().count() + 1;
166     EXPECT_THAT(setProperty(sut->getPath(), "Interval", newValue).value(),
167                 Eq(boost::system::errc::success));
168     EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "Interval"),
169                 Eq(newValue));
170 }
171 
172 TEST_F(TestReport, settingIntervalWithInvalidValueDoesNotChangeProperty)
173 {
174     uint64_t newValue = defaultParams.interval().count() - 1;
175     EXPECT_THAT(setProperty(sut->getPath(), "Interval", newValue).value(),
176                 Eq(boost::system::errc::success));
177     EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "Interval"),
178                 Eq(defaultParams.interval().count()));
179 }
180 
181 TEST_F(TestReport, settingEmitsReadingsUpdateHaveNoEffect)
182 {
183     EXPECT_THAT(setProperty(sut->getPath(), "EmitsReadingsUpdate",
184                             !defaultParams.emitReadingUpdate())
185                     .value(),
186                 Eq(boost::system::errc::read_only_file_system));
187     EXPECT_THAT(getProperty<bool>(sut->getPath(), "EmitsReadingsUpdate"),
188                 Eq(defaultParams.emitReadingUpdate()));
189 }
190 
191 TEST_F(TestReport, settingLogToMetricReportCollectionHaveNoEffect)
192 {
193     EXPECT_THAT(setProperty(sut->getPath(), "LogToMetricReportsCollection",
194                             !defaultParams.logToMetricReportCollection())
195                     .value(),
196                 Eq(boost::system::errc::read_only_file_system));
197     EXPECT_THAT(
198         getProperty<bool>(sut->getPath(), "LogToMetricReportsCollection"),
199         Eq(defaultParams.logToMetricReportCollection()));
200 }
201 
202 TEST_F(TestReport, settingPersistencyToFalseRemovesReportFromStorage)
203 {
204     EXPECT_CALL(storageMock, remove(to_file_path(sut->getName())));
205 
206     bool persistency = false;
207     EXPECT_THAT(setProperty(sut->getPath(), "Persistency", persistency).value(),
208                 Eq(boost::system::errc::success));
209     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistency"),
210                 Eq(persistency));
211 }
212 
213 TEST_F(TestReport, deleteReport)
214 {
215     EXPECT_CALL(*reportManagerMock, removeReport(sut.get()));
216     auto ec = deleteReport(sut->getPath());
217     EXPECT_THAT(ec, Eq(boost::system::errc::success));
218 }
219 
220 TEST_F(TestReport, deletingNonExistingReportReturnInvalidRequestDescriptor)
221 {
222     auto ec = deleteReport(Report::reportDir + "NonExisting"s);
223     EXPECT_THAT(ec.value(), Eq(EBADR));
224 }
225 
226 TEST_F(TestReport, deleteReportExpectThatFileIsRemoveFromStorage)
227 {
228     EXPECT_CALL(storageMock, remove(to_file_path(sut->getName())));
229     auto ec = deleteReport(sut->getPath());
230     EXPECT_THAT(ec, Eq(boost::system::errc::success));
231 }
232 
233 class TestReportStore :
234     public TestReport,
235     public WithParamInterface<std::pair<std::string, nlohmann::json>>
236 {
237   public:
238     void SetUp() override
239     {}
240 
241     nlohmann::json storedConfiguration;
242 };
243 
244 INSTANTIATE_TEST_SUITE_P(
245     _, TestReportStore,
246     Values(std::make_pair("Version"s, nlohmann::json(3)),
247            std::make_pair("Name"s, nlohmann::json(ReportParams().reportName())),
248            std::make_pair("ReportingType",
249                           nlohmann::json(ReportParams().reportingType())),
250            std::make_pair("EmitsReadingsUpdate",
251                           nlohmann::json(ReportParams().emitReadingUpdate())),
252            std::make_pair(
253                "LogToMetricReportsCollection",
254                nlohmann::json(ReportParams().logToMetricReportCollection())),
255            std::make_pair("Interval",
256                           nlohmann::json(ReportParams().interval().count())),
257            std::make_pair(
258                "ReadingParameters",
259                nlohmann::json(
260                    {{{tstring::SensorPath::str(),
261                       {{"service", "service0"}, {"path", "path0"}}},
262                      {tstring::OperationType::str(), OperationType::single},
263                      {tstring::Id::str(), "id0"},
264                      {tstring::MetricMetadata::str(), "metadata0"}},
265                     {{tstring::SensorPath::str(),
266                       {{"service", "service1"}, {"path", "path1"}}},
267                      {tstring::OperationType::str(), OperationType::max},
268                      {tstring::Id::str(), "id1"},
269                      {tstring::MetricMetadata::str(), "metadata1"}},
270                     {{tstring::SensorPath::str(),
271                       {{"service", "service2"}, {"path", "path2"}}},
272                      {tstring::OperationType::str(), OperationType::min},
273                      {tstring::Id::str(), "id2"},
274                      {tstring::MetricMetadata::str(), "metadata2"}}}))));
275 
276 TEST_P(TestReportStore, settingPersistencyToTrueStoresReport)
277 {
278     sut = makeReport(ReportParams());
279 
280     {
281         InSequence seq;
282         EXPECT_CALL(storageMock, remove(to_file_path(sut->getName())));
283         EXPECT_CALL(checkPoint, Call());
284         EXPECT_CALL(storageMock, store(to_file_path(sut->getName()), _))
285             .WillOnce(SaveArg<1>(&storedConfiguration));
286     }
287 
288     setProperty(sut->getPath(), "Persistency", false);
289     checkPoint.Call();
290     setProperty(sut->getPath(), "Persistency", true);
291 
292     const auto& [key, value] = GetParam();
293 
294     ASSERT_THAT(storedConfiguration.at(key), Eq(value));
295 }
296 
297 TEST_P(TestReportStore, reportIsSavedToStorageAfterCreated)
298 {
299     EXPECT_CALL(storageMock,
300                 store(to_file_path(ReportParams().reportName()), _))
301         .WillOnce(SaveArg<1>(&storedConfiguration));
302 
303     sut = makeReport(ReportParams());
304 
305     const auto& [key, value] = GetParam();
306 
307     ASSERT_THAT(storedConfiguration.at(key), Eq(value));
308 }
309 
310 class TestReportValidNames :
311     public TestReport,
312     public WithParamInterface<ReportParams>
313 {
314   public:
315     void SetUp() override
316     {}
317 };
318 
319 INSTANTIATE_TEST_SUITE_P(
320     ValidNames, TestReportValidNames,
321     Values(ReportParams().reportName("Valid_1"),
322            ReportParams().reportName("Valid_1/Valid_2"),
323            ReportParams().reportName("Valid_1/Valid_2/Valid_3")));
324 
325 TEST_P(TestReportValidNames, reportCtorDoesNotThrowOnValidName)
326 {
327     EXPECT_NO_THROW(makeReport(GetParam()));
328 }
329 
330 class TestReportInvalidNames :
331     public TestReport,
332     public WithParamInterface<ReportParams>
333 {
334   public:
335     void SetUp() override
336     {}
337 };
338 
339 INSTANTIATE_TEST_SUITE_P(InvalidNames, TestReportInvalidNames,
340                          Values(ReportParams().reportName("/"),
341                                 ReportParams().reportName("/Invalid"),
342                                 ReportParams().reportName("Invalid/"),
343                                 ReportParams().reportName("Invalid/Invalid/"),
344                                 ReportParams().reportName("Invalid?")));
345 
346 TEST_P(TestReportInvalidNames, reportCtorThrowOnInvalidName)
347 {
348     EXPECT_THROW(makeReport(GetParam()), sdbusplus::exception::SdBusError);
349 }
350 
351 TEST_F(TestReportInvalidNames, reportCtorThrowOnInvalidNameAndNoStoreIsCalled)
352 {
353     EXPECT_CALL(storageMock, store).Times(0);
354     EXPECT_THROW(makeReport(ReportParams().reportName("/Invalid")),
355                  sdbusplus::exception::SdBusError);
356 }
357 
358 class TestReportAllReportTypes :
359     public TestReport,
360     public WithParamInterface<ReportParams>
361 {
362   public:
363     void SetUp() override
364     {
365         sut = makeReport(GetParam());
366     }
367 };
368 
369 INSTANTIATE_TEST_SUITE_P(_, TestReportAllReportTypes,
370                          Values(ReportParams().reportingType("OnRequest"),
371                                 ReportParams().reportingType("OnChange"),
372                                 ReportParams().reportingType("Periodic")));
373 
374 TEST_P(TestReportAllReportTypes, returnPropertValueOfReportType)
375 {
376     EXPECT_THAT(getProperty<std::string>(sut->getPath(), "ReportingType"),
377                 Eq(GetParam().reportingType()));
378 }
379 
380 TEST_P(TestReportAllReportTypes, updateReadingsCallUpdateReadingsProperty)
381 {
382     const uint64_t expectedTime = std::time(0);
383 
384     sut->updateReadings();
385 
386     const auto [timestamp, readings] =
387         getProperty<Readings>(sut->getPath(), "Readings");
388 
389     EXPECT_THAT(timestamp, Ge(expectedTime));
390 }
391 
392 class TestReportOnRequestType : public TestReport
393 {
394     void SetUp() override
395     {
396         sut = makeReport(ReportParams().reportingType("OnRequest"));
397     }
398 };
399 
400 TEST_F(TestReportOnRequestType, updatesReadingTimestamp)
401 {
402     const uint64_t expectedTime = std::time(0);
403 
404     ASSERT_THAT(update(sut->getPath()), Eq(boost::system::errc::success));
405 
406     const auto [timestamp, readings] =
407         getProperty<Readings>(sut->getPath(), "Readings");
408 
409     EXPECT_THAT(timestamp, Ge(expectedTime));
410 }
411 
412 TEST_F(TestReportOnRequestType, updatesReadingWhenUpdateIsCalled)
413 {
414     ASSERT_THAT(update(sut->getPath()), Eq(boost::system::errc::success));
415 
416     const auto [timestamp, readings] =
417         getProperty<Readings>(sut->getPath(), "Readings");
418 
419     EXPECT_THAT(readings,
420                 ElementsAre(std::make_tuple("a"s, "b"s, 17.1, 114u),
421                             std::make_tuple("aa"s, "bb"s, 42.0, 74u),
422                             std::make_tuple("aaa"s, "bbb"s, 100.7, 21u)));
423 }
424 
425 class TestReportNonOnRequestType :
426     public TestReport,
427     public WithParamInterface<ReportParams>
428 {
429     void SetUp() override
430     {
431         sut = makeReport(GetParam());
432     }
433 };
434 
435 INSTANTIATE_TEST_SUITE_P(_, TestReportNonOnRequestType,
436                          Values(ReportParams().reportingType("Periodic"),
437                                 ReportParams().reportingType("OnChange")));
438 
439 TEST_P(TestReportNonOnRequestType, readingsAreNotUpdateOnUpdateCall)
440 {
441     ASSERT_THAT(update(sut->getPath()), Eq(boost::system::errc::success));
442 
443     EXPECT_THAT(getProperty<Readings>(sut->getPath(), "Readings"),
444                 Eq(Readings{}));
445 }
446 
447 class TestReportNonPeriodicReport :
448     public TestReport,
449     public WithParamInterface<ReportParams>
450 {
451   public:
452     void SetUp() override
453     {
454         sut = makeReport(GetParam());
455     }
456 };
457 
458 INSTANTIATE_TEST_SUITE_P(_, TestReportNonPeriodicReport,
459                          Values(ReportParams().reportingType("OnRequest"),
460                                 ReportParams().reportingType("OnChange")));
461 
462 TEST_P(TestReportNonPeriodicReport, readingsAreNotUpdatedAfterIntervalExpires)
463 {
464     DbusEnvironment::sleepFor(ReportManager::minInterval + 1ms);
465 
466     EXPECT_THAT(getProperty<Readings>(sut->getPath(), "Readings"),
467                 Eq(Readings{}));
468 }
469 
470 class TestReportPeriodicReport : public TestReport
471 {
472     void SetUp() override
473     {
474         sut = makeReport(ReportParams().reportingType("Periodic"));
475     }
476 };
477 
478 TEST_F(TestReportPeriodicReport, readingTimestampIsUpdatedAfterIntervalExpires)
479 {
480     const uint64_t expectedTime = std::time(0);
481     DbusEnvironment::sleepFor(ReportManager::minInterval + 1ms);
482 
483     const auto [timestamp, readings] =
484         getProperty<Readings>(sut->getPath(), "Readings");
485 
486     EXPECT_THAT(timestamp, Ge(expectedTime));
487 }
488 
489 TEST_F(TestReportPeriodicReport, readingsAreUpdatedAfterIntervalExpires)
490 {
491     DbusEnvironment::sleepFor(ReportManager::minInterval + 1ms);
492 
493     const auto [timestamp, readings] =
494         getProperty<Readings>(sut->getPath(), "Readings");
495 
496     EXPECT_THAT(readings,
497                 ElementsAre(std::make_tuple("a"s, "b"s, 17.1, 114u),
498                             std::make_tuple("aa"s, "bb"s, 42.0, 74u),
499                             std::make_tuple("aaa"s, "bbb"s, 100.7, 21u)));
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