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