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