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