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