1 #include "config.h"
2 
3 #include "elog_entry.hpp"
4 #include "log_manager.hpp"
5 
6 #include <filesystem>
7 #include <sdbusplus/bus.hpp>
8 #include <sdbusplus/test/sdbus_mock.hpp>
9 
10 #include <gmock/gmock.h>
11 #include <gtest/gtest.h>
12 
13 namespace phosphor
14 {
15 namespace logging
16 {
17 namespace test
18 {
19 
20 class TestQuiesceOnError : public testing::Test
21 {
22   public:
23     sdbusplus::SdBusMock sdbusMock;
24     sdbusplus::bus_t mockedBus = sdbusplus::get_mocked_new(&sdbusMock);
25     phosphor::logging::internal::Manager manager;
26 
27     TestQuiesceOnError() : manager(mockedBus, OBJ_INTERNAL)
28     {
29         // Ensure any errors serializing to filesystem have directory created
30         std::filesystem::create_directory(ERRLOG_PERSIST_PATH);
31     }
32 };
33 
34 // Test that false is returned when no callout is present in the log
35 TEST_F(TestQuiesceOnError, testNoCallout)
36 {
37     uint32_t id = 99;
38     uint64_t timestamp{100};
39     std::string message{"test error"};
40     std::string fwLevel{"level42"};
41     std::string path{"/tmp/99"};
42     std::vector<std::string> testData{"no", "callout"};
43     phosphor::logging::AssociationList associations{};
44 
45     Entry elog{mockedBus,
46                std::string(OBJ_ENTRY) + '/' + std::to_string(id),
47                id,
48                timestamp,
49                Entry::Level::Informational,
50                std::move(message),
51                std::move(testData),
52                std::move(associations),
53                fwLevel,
54                path,
55                manager};
56 
57     EXPECT_EQ(manager.isCalloutPresent(elog), false);
58 }
59 
60 // Test that trues is returned when a callout is present in the log
61 TEST_F(TestQuiesceOnError, testCallout)
62 {
63     uint32_t id = 99;
64     uint64_t timestamp{100};
65     std::string message{"test error"};
66     std::string fwLevel{"level42"};
67     std::string path{"/tmp/99"};
68     std::vector<std::string> testData{
69         "CALLOUT_INVENTORY_PATH=/xyz/openbmc_project/inventory/system/chassis/"
70         "motherboard/powersupply0/"};
71     phosphor::logging::AssociationList associations{};
72 
73     Entry elog{mockedBus,
74                std::string(OBJ_ENTRY) + '/' + std::to_string(id),
75                id,
76                timestamp,
77                Entry::Level::Informational,
78                std::move(message),
79                std::move(testData),
80                std::move(associations),
81                fwLevel,
82                path,
83                manager};
84 
85     EXPECT_EQ(manager.isCalloutPresent(elog), true);
86 }
87 
88 // Test that a blocking error is created on entry with callout
89 TEST_F(TestQuiesceOnError, testBlockingErrorsCreated)
90 {
91     uint32_t id = 100;
92     uint64_t timestamp{100};
93     std::string message{"test error"};
94     std::string fwLevel{"level42"};
95     std::string path{"/tmp/99"};
96     std::vector<std::string> testData{
97         "CALLOUT_INVENTORY_PATH=/xyz/openbmc_project/inventory/system/chassis/"
98         "motherboard/powersupply0/"};
99     phosphor::logging::AssociationList associations{};
100 
101     // Ensure D-Bus object created for this blocking error
102     // First allow any number of sd_bus_emit_object_added calls
103     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(testing::_, testing::_))
104         .Times(testing::AnyNumber());
105     // Second verify the new block100 object is created once
106     EXPECT_CALL(sdbusMock,
107                 sd_bus_emit_object_added(
108                     testing::_, testing::HasSubstr(
109                                     "/xyz/openbmc_project/logging/block100")))
110         .Times(1);
111 
112     Entry elog{mockedBus,
113                std::string(OBJ_ENTRY) + '/' + std::to_string(id),
114                id,
115                timestamp,
116                Entry::Level::Informational,
117                std::move(message),
118                std::move(testData),
119                std::move(associations),
120                fwLevel,
121                path,
122                manager};
123 
124     manager.quiesceOnError(id);
125     // Created error with callout so expect a blocking error now
126     EXPECT_EQ(manager.getBlockingErrSize(), 1);
127 
128     // Now delete the error and make sure the object and entry go away
129     EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(testing::_, testing::_))
130         .Times(testing::AnyNumber());
131     EXPECT_CALL(sdbusMock,
132                 sd_bus_emit_object_removed(
133                     testing::_, testing::HasSubstr(
134                                     "/xyz/openbmc_project/logging/block100")))
135         .Times(1);
136 
137     // Make sure nothing happens within invalid id
138     manager.checkAndRemoveBlockingError(id + 1);
139     EXPECT_EQ(manager.getBlockingErrSize(), 1);
140 
141     manager.checkAndRemoveBlockingError(id);
142     EXPECT_EQ(manager.getBlockingErrSize(), 0);
143 }
144 
145 // Test that a blocking error is created on entry with callout
146 TEST_F(TestQuiesceOnError, testBlockingErrorsResolved)
147 {
148     uint32_t id = 101;
149     uint64_t timestamp{100};
150     std::string message{"test error"};
151     std::string fwLevel{"level42"};
152     std::string path{"/tmp/99"};
153     std::vector<std::string> testData{
154         "CALLOUT_INVENTORY_PATH=/xyz/openbmc_project/inventory/system/chassis/"
155         "motherboard/powersupply0/"};
156     phosphor::logging::AssociationList associations{};
157 
158     // Ensure D-Bus object created for this blocking error
159     // First allow any number of sd_bus_emit_object_added calls
160     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(testing::_, testing::_))
161         .Times(testing::AnyNumber());
162     // Second verify the new block100 object is created once
163     EXPECT_CALL(sdbusMock,
164                 sd_bus_emit_object_added(
165                     testing::_, testing::HasSubstr(
166                                     "/xyz/openbmc_project/logging/block101")))
167         .Times(1);
168 
169     Entry elog{mockedBus,
170                std::string(OBJ_ENTRY) + '/' + std::to_string(id),
171                id,
172                timestamp,
173                Entry::Level::Informational,
174                std::move(message),
175                std::move(testData),
176                std::move(associations),
177                fwLevel,
178                path,
179                manager};
180 
181     manager.quiesceOnError(id);
182     // Created error with callout so expect a blocking error now
183     EXPECT_EQ(manager.getBlockingErrSize(), 1);
184     // Also should have a callback create looking for entry to be resolved
185     EXPECT_EQ(manager.getEntryCallbackSize(), 1);
186 
187     // Now resolve the error and make sure the object and entry go away
188     EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(testing::_, testing::_))
189         .Times(testing::AnyNumber());
190     EXPECT_CALL(sdbusMock,
191                 sd_bus_emit_object_removed(
192                     testing::_, testing::HasSubstr(
193                                     "/xyz/openbmc_project/logging/block101")))
194         .Times(1);
195 
196     elog.resolved(true);
197     // Note that property signal callbacks do not work in unit test so directly
198     // call the interface to find and resolve blocking entries
199     manager.checkAndRemoveBlockingError(101);
200     EXPECT_EQ(manager.getBlockingErrSize(), 0);
201     EXPECT_EQ(manager.getEntryCallbackSize(), 0);
202 }
203 
204 // Test that a blocking error is only created once for an individual bmc id
205 TEST_F(TestQuiesceOnError, testBlockingErrorTwice)
206 {
207     uint32_t id = 100;
208     uint64_t timestamp{100};
209     std::string message{"test error"};
210     std::string fwLevel{"level42"};
211     std::string path{"/tmp/99"};
212     std::vector<std::string> testData{
213         "CALLOUT_INVENTORY_PATH=/xyz/openbmc_project/inventory/system/chassis/"
214         "motherboard/powersupply0/"};
215     phosphor::logging::AssociationList associations{};
216 
217     // Ensure D-Bus object created for this blocking error
218     // First allow any number of sd_bus_emit_object_added calls
219     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(testing::_, testing::_))
220         .Times(testing::AnyNumber());
221     // Second verify the new block100 object is created once
222     EXPECT_CALL(sdbusMock,
223                 sd_bus_emit_object_added(
224                     testing::_, testing::HasSubstr(
225                                     "/xyz/openbmc_project/logging/block100")))
226         .Times(1);
227 
228     Entry elog{mockedBus,
229                std::string(OBJ_ENTRY) + '/' + std::to_string(id),
230                id,
231                timestamp,
232                Entry::Level::Informational,
233                std::move(message),
234                std::move(testData),
235                std::move(associations),
236                fwLevel,
237                path,
238                manager};
239 
240     manager.quiesceOnError(id);
241     // Created error with callout so expect a blocking error now
242     EXPECT_EQ(manager.getBlockingErrSize(), 1);
243 
244     // Now pass in same ID and make sure it's ignored
245     manager.quiesceOnError(id);
246 
247     // Now delete the error and make sure the object and entry go away
248     EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(testing::_, testing::_))
249         .Times(testing::AnyNumber());
250     EXPECT_CALL(sdbusMock,
251                 sd_bus_emit_object_removed(
252                     testing::_, testing::HasSubstr(
253                                     "/xyz/openbmc_project/logging/block100")))
254         .Times(1);
255 
256     manager.checkAndRemoveBlockingError(id);
257     EXPECT_EQ(manager.getBlockingErrSize(), 0);
258 }
259 
260 } // namespace test
261 } // namespace logging
262 } // namespace phosphor
263