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