1 #include "extensions/openpower-pels/data_interface.hpp"
2 #include "extensions/openpower-pels/host_interface.hpp"
3 
4 #include <fcntl.h>
5 
6 #include <filesystem>
7 #include <sdeventplus/source/io.hpp>
8 
9 #include <gmock/gmock.h>
10 
11 namespace openpower
12 {
13 namespace pels
14 {
15 
16 class MockDataInterface : public DataInterfaceBase
17 {
18   public:
19     MockDataInterface()
20     {
21     }
22     MOCK_METHOD(std::string, getMachineTypeModel, (), (const override));
23     MOCK_METHOD(std::string, getMachineSerialNumber, (), (const override));
24     MOCK_METHOD(std::string, getServerFWVersion, (), (const override));
25     MOCK_METHOD(std::string, getBMCFWVersion, (), (const override));
26     MOCK_METHOD(std::string, getBMCFWVersionID, (), (const override));
27     MOCK_METHOD(bool, getHostPELEnablement, (), (const override));
28     MOCK_METHOD(std::string, getBMCState, (), (const override));
29     MOCK_METHOD(std::string, getChassisState, (), (const override));
30     MOCK_METHOD(std::string, getHostState, (), (const override));
31     MOCK_METHOD(std::string, getMotherboardCCIN, (), (const override));
32     MOCK_METHOD(void, getHWCalloutFields,
33                 (const std::string&, std::string&, std::string&, std::string&),
34                 (const override));
35     MOCK_METHOD(std::string, getSystemType, (), (const override));
36     MOCK_METHOD(std::string, getLocationCode, (const std::string&),
37                 (const override));
38 
39     void changeHostState(bool newState)
40     {
41         setHostUp(newState);
42     }
43 
44     void setHMCManaged(bool managed)
45     {
46         _hmcManaged = managed;
47     }
48 };
49 
50 /**
51  * @brief The mock HostInterface class
52  *
53  * This replaces the PLDM calls with a FIFO for the asynchronous
54  * responses.
55  */
56 class MockHostInterface : public HostInterface
57 {
58   public:
59     /**
60      * @brief Constructor
61      *
62      * @param[in] event - The sd_event object
63      * @param[in] dataIface - The DataInterface class
64      */
65     MockHostInterface(sd_event* event, DataInterfaceBase& dataIface) :
66         HostInterface(event, dataIface)
67     {
68         char templ[] = "/tmp/cmdfifoXXXXXX";
69         std::filesystem::path dir = mkdtemp(templ);
70         _fifo = dir / "fifo";
71     }
72 
73     /**
74      * @brief Destructor
75      */
76     virtual ~MockHostInterface()
77     {
78         std::filesystem::remove_all(_fifo.parent_path());
79     }
80 
81     MOCK_METHOD(CmdStatus, sendNewLogCmd, (uint32_t, uint32_t), (override));
82 
83     /**
84      * @brief Cancels waiting for a command response
85      */
86     virtual void cancelCmd() override
87     {
88         _inProgress = false;
89         _source = nullptr;
90     }
91 
92     /**
93      * @brief Returns the amount of time to wait before retrying after
94      *        a failed send command.
95      *
96      * @return milliseconds - The amount of time to wait
97      */
98     virtual std::chrono::milliseconds getSendRetryDelay() const override
99     {
100         return std::chrono::milliseconds(2);
101     }
102 
103     /**
104      * @brief Returns the amount of time to wait before retrying after
105      *        a command receive.
106      *
107      * @return milliseconds - The amount of time to wait
108      */
109     virtual std::chrono::milliseconds getReceiveRetryDelay() const override
110     {
111         return std::chrono::milliseconds(2);
112     }
113 
114     /**
115      * @brief Returns the amount of time to wait before retrying if the
116      *        host firmware's PEL storage was full and it can't store
117      *        any more logs until it is freed up somehow.
118      *
119      * @return milliseconds - The amount of time to wait
120      */
121     virtual std::chrono::milliseconds getHostFullRetryDelay() const override
122     {
123         return std::chrono::milliseconds(400);
124     }
125 
126     /**
127      * @brief Returns the number of commands processed
128      */
129     size_t numCmdsProcessed() const
130     {
131         return _cmdsProcessed;
132     }
133 
134     /**
135      * @brief Writes the data passed in to the FIFO
136      *
137      * @param[in] hostResponse - use a 0 to indicate success
138      *
139      * @return CmdStatus - success or failure
140      */
141     CmdStatus send(uint8_t hostResponse)
142     {
143         // Create a FIFO once.
144         if (!std::filesystem::exists(_fifo))
145         {
146             if (mkfifo(_fifo.c_str(), 0622))
147             {
148                 ADD_FAILURE() << "Failed mkfifo " << _fifo << strerror(errno);
149                 exit(-1);
150             }
151         }
152 
153         // Open it and register the reponse callback to
154         // be used on FD activity.
155         int fd = open(_fifo.c_str(), O_NONBLOCK | O_RDWR);
156         EXPECT_TRUE(fd >= 0) << "Unable to open FIFO";
157 
158         auto callback = [this](sdeventplus::source::IO& source, int fd,
159                                uint32_t events) {
160             this->receive(source, fd, events);
161         };
162 
163         try
164         {
165             _source = std::make_unique<sdeventplus::source::IO>(
166                 _event, fd, EPOLLIN,
167                 std::bind(callback, std::placeholders::_1,
168                           std::placeholders::_2, std::placeholders::_3));
169         }
170         catch (std::exception& e)
171         {
172             ADD_FAILURE() << "Event exception: " << e.what();
173             close(fd);
174             return CmdStatus::failure;
175         }
176 
177         // Write the fake host reponse to the FIFO
178         auto bytesWritten = write(fd, &hostResponse, sizeof(hostResponse));
179         EXPECT_EQ(bytesWritten, sizeof(hostResponse));
180 
181         _inProgress = true;
182 
183         return CmdStatus::success;
184     }
185 
186   protected:
187     /**
188      * @brief Reads the data written to the fifo and then calls
189      *        the subscriber's callback.
190      *
191      * Nonzero data indicates a command failure (for testing bad path).
192      *
193      * @param[in] source - The event source object
194      * @param[in] fd - The file descriptor used
195      * @param[in] events - The event bits
196      */
197     void receive(sdeventplus::source::IO& source, int fd,
198                  uint32_t events) override
199     {
200         if (!(events & EPOLLIN))
201         {
202             return;
203         }
204 
205         _inProgress = false;
206 
207         int newFD = open(_fifo.c_str(), O_NONBLOCK | O_RDONLY);
208         ASSERT_TRUE(newFD >= 0) << "Failed to open FIFO";
209 
210         // Read the host success/failure response from the FIFO.
211         uint8_t data;
212         auto bytesRead = read(newFD, &data, sizeof(data));
213         EXPECT_EQ(bytesRead, sizeof(data));
214 
215         close(newFD);
216 
217         ResponseStatus status = ResponseStatus::success;
218         if (data != 0)
219         {
220             status = ResponseStatus::failure;
221         }
222 
223         callResponseFunc(status);
224 
225         // Keep account of the number of commands responses for testing.
226         _cmdsProcessed++;
227     }
228 
229   private:
230     /**
231      * @brief The event source for the fifo
232      */
233     std::unique_ptr<sdeventplus::source::IO> _source;
234 
235     /**
236      * @brief the path to the fifo
237      */
238     std::filesystem::path _fifo;
239 
240     /**
241      * @brief The number of commands processed
242      */
243     size_t _cmdsProcessed = 0;
244 };
245 
246 } // namespace pels
247 } // namespace openpower
248