1 #include "activation.hpp"
2 #include "mocked_activation_listener.hpp"
3 #include "mocked_association_interface.hpp"
4 #include "mocked_utils.hpp"
5 
6 #include <sdbusplus/test/sdbus_mock.hpp>
7 
8 #include <gmock/gmock.h>
9 #include <gtest/gtest.h>
10 
11 using namespace phosphor::software::updater;
12 
13 using ::testing::_;
14 using ::testing::Return;
15 using ::testing::StrEq;
16 
17 using std::experimental::any;
18 
19 class TestActivation : public ::testing::Test
20 {
21   public:
22     using PropertyType = utils::UtilsInterface::PropertyType;
23     using Status = Activation::Status;
24     using RequestedStatus = Activation::RequestedActivations;
25     TestActivation() :
26         mockedUtils(
27             reinterpret_cast<const utils::MockedUtils&>(utils::getUtils()))
28     {
29         // By default make it compatible with the test software
30         ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(MANUFACTURER)))
31             .WillByDefault(Return(any(PropertyType(std::string("TestManu")))));
32         ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(MODEL)))
33             .WillByDefault(Return(any(PropertyType(std::string("TestModel")))));
34         ON_CALL(mockedUtils, isAssociated(_, _)).WillByDefault(Return(false));
35     }
36     ~TestActivation()
37     {
38         utils::freeUtils();
39     }
40 
41     void onUpdateDone()
42     {
43         activation->onUpdateDone();
44     }
45     void onUpdateFailed()
46     {
47         activation->onUpdateFailed();
48     }
49     int getProgress()
50     {
51         return activation->activationProgress->progress();
52     }
53     const auto& getPsuQueue()
54     {
55         return activation->psuQueue;
56     }
57     std::string getUpdateService(const std::string& psuInventoryPath)
58     {
59         return activation->getUpdateService(psuInventoryPath);
60     }
61 
62     sdbusplus::SdBusMock sdbusMock;
63     sdbusplus::bus::bus mockedBus = sdbusplus::get_mocked_new(&sdbusMock);
64     const utils::MockedUtils& mockedUtils;
65     MockedAssociationInterface mockedAssociationInterface;
66     MockedActivationListener mockedActivationListener;
67     std::unique_ptr<Activation> activation;
68     std::string versionId = "abcdefgh";
69     std::string extVersion = "manufacturer=TestManu,model=TestModel";
70     std::string filePath = "/tmp/images/abcdefgh";
71     std::string dBusPath = std::string(SOFTWARE_OBJPATH) + "/" + versionId;
72     Status status = Status::Ready;
73     AssociationList associations;
74 };
75 
76 TEST_F(TestActivation, ctordtor)
77 {
78     activation = std::make_unique<Activation>(
79         mockedBus, dBusPath, versionId, extVersion, status, associations,
80         filePath, &mockedAssociationInterface, &mockedActivationListener);
81 }
82 
83 TEST_F(TestActivation, ctorWithInvalidExtVersion)
84 {
85     extVersion = "invalid text";
86     activation = std::make_unique<Activation>(
87         mockedBus, dBusPath, versionId, extVersion, status, associations,
88         filePath, &mockedAssociationInterface, &mockedActivationListener);
89 }
90 
91 TEST_F(TestActivation, getUpdateService)
92 {
93     std::string psuInventoryPath = "/com/example/inventory/powersupply1";
94     std::string toCompare = "psu-update@-com-example-inventory-"
95                             "powersupply1\\x20-tmp-images-12345678.service";
96     versionId = "12345678";
97     filePath = "/tmp/images/12345678";
98 
99     activation = std::make_unique<Activation>(
100         mockedBus, dBusPath, versionId, extVersion, status, associations,
101         filePath, &mockedAssociationInterface, &mockedActivationListener);
102 
103     auto service = getUpdateService(psuInventoryPath);
104     EXPECT_EQ(toCompare, service);
105 }
106 
107 TEST_F(TestActivation, doUpdateWhenNoPSU)
108 {
109     activation = std::make_unique<Activation>(
110         mockedBus, dBusPath, versionId, extVersion, status, associations,
111         filePath, &mockedAssociationInterface, &mockedActivationListener);
112     ON_CALL(mockedUtils, getPSUInventoryPath(_))
113         .WillByDefault(
114             Return(std::vector<std::string>({}))); // No PSU inventory
115     activation->requestedActivation(RequestedStatus::Active);
116 
117     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
118         .Times(0);
119     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
120         .Times(0);
121     EXPECT_CALL(mockedActivationListener, onUpdateDone(_, _)).Times(0);
122     EXPECT_EQ(Status::Failed, activation->activation());
123 }
124 
125 TEST_F(TestActivation, doUpdateOnePSUOK)
126 {
127     constexpr auto psu0 = "/com/example/inventory/psu0";
128     activation = std::make_unique<Activation>(
129         mockedBus, dBusPath, versionId, extVersion, status, associations,
130         filePath, &mockedAssociationInterface, &mockedActivationListener);
131     ON_CALL(mockedUtils, getPSUInventoryPath(_))
132         .WillByDefault(
133             Return(std::vector<std::string>({psu0}))); // One PSU inventory
134     activation->requestedActivation(RequestedStatus::Active);
135 
136     EXPECT_EQ(Status::Activating, activation->activation());
137 
138     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
139         .Times(1);
140     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
141         .Times(1);
142     EXPECT_CALL(mockedActivationListener,
143                 onUpdateDone(StrEq(versionId), StrEq(psu0)))
144         .Times(1);
145     onUpdateDone();
146     EXPECT_EQ(Status::Active, activation->activation());
147 }
148 
149 TEST_F(TestActivation, doUpdateFourPSUsOK)
150 {
151     constexpr auto psu0 = "/com/example/inventory/psu0";
152     constexpr auto psu1 = "/com/example/inventory/psu1";
153     constexpr auto psu2 = "/com/example/inventory/psu2";
154     constexpr auto psu3 = "/com/example/inventory/psu3";
155     activation = std::make_unique<Activation>(
156         mockedBus, dBusPath, versionId, extVersion, status, associations,
157         filePath, &mockedAssociationInterface, &mockedActivationListener);
158     ON_CALL(mockedUtils, getPSUInventoryPath(_))
159         .WillByDefault(Return(
160             std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs
161     activation->requestedActivation(RequestedStatus::Active);
162 
163     EXPECT_EQ(Status::Activating, activation->activation());
164     EXPECT_EQ(10, getProgress());
165 
166     EXPECT_CALL(mockedActivationListener,
167                 onUpdateDone(StrEq(versionId), StrEq(psu0)))
168         .Times(1);
169     onUpdateDone();
170     EXPECT_EQ(Status::Activating, activation->activation());
171     EXPECT_EQ(30, getProgress());
172 
173     EXPECT_CALL(mockedActivationListener,
174                 onUpdateDone(StrEq(versionId), StrEq(psu1)))
175         .Times(1);
176     onUpdateDone();
177     EXPECT_EQ(Status::Activating, activation->activation());
178     EXPECT_EQ(50, getProgress());
179 
180     EXPECT_CALL(mockedActivationListener,
181                 onUpdateDone(StrEq(versionId), StrEq(psu2)))
182         .Times(1);
183     onUpdateDone();
184     EXPECT_EQ(Status::Activating, activation->activation());
185     EXPECT_EQ(70, getProgress());
186 
187     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
188         .Times(1);
189     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
190         .Times(1);
191 
192     EXPECT_CALL(mockedActivationListener,
193                 onUpdateDone(StrEq(versionId), StrEq(psu3)))
194         .Times(1);
195     onUpdateDone();
196     EXPECT_EQ(Status::Active, activation->activation());
197 }
198 
199 TEST_F(TestActivation, doUpdateFourPSUsFailonSecond)
200 {
201     constexpr auto psu0 = "/com/example/inventory/psu0";
202     constexpr auto psu1 = "/com/example/inventory/psu1";
203     constexpr auto psu2 = "/com/example/inventory/psu2";
204     constexpr auto psu3 = "/com/example/inventory/psu3";
205     activation = std::make_unique<Activation>(
206         mockedBus, dBusPath, versionId, extVersion, status, associations,
207         filePath, &mockedAssociationInterface, &mockedActivationListener);
208     ON_CALL(mockedUtils, getPSUInventoryPath(_))
209         .WillByDefault(Return(
210             std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs
211     activation->requestedActivation(RequestedStatus::Active);
212 
213     EXPECT_EQ(Status::Activating, activation->activation());
214     EXPECT_EQ(10, getProgress());
215 
216     EXPECT_CALL(mockedActivationListener,
217                 onUpdateDone(StrEq(versionId), StrEq(psu0)))
218         .Times(1);
219     onUpdateDone();
220     EXPECT_EQ(Status::Activating, activation->activation());
221     EXPECT_EQ(30, getProgress());
222 
223     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
224         .Times(0);
225     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
226         .Times(0);
227     EXPECT_CALL(mockedActivationListener, onUpdateDone(_, _)).Times(0);
228     onUpdateFailed();
229     EXPECT_EQ(Status::Failed, activation->activation());
230 }
231 
232 TEST_F(TestActivation, doUpdateOnExceptionFromDbus)
233 {
234     constexpr auto psu0 = "/com/example/inventory/psu0";
235     activation = std::make_unique<Activation>(
236         mockedBus, dBusPath, versionId, extVersion, status, associations,
237         filePath, &mockedAssociationInterface, &mockedActivationListener);
238     ON_CALL(mockedUtils, getPSUInventoryPath(_))
239         .WillByDefault(
240             Return(std::vector<std::string>({psu0}))); // One PSU inventory
241     ON_CALL(sdbusMock, sd_bus_call(_, _, _, _, nullptr))
242         .WillByDefault(Return(-1)); // Make sdbus call failure
243     activation->requestedActivation(RequestedStatus::Active);
244 
245     EXPECT_EQ(Status::Failed, activation->activation());
246 }
247 
248 TEST_F(TestActivation, doUpdateOnePSUModelNotCompatible)
249 {
250     constexpr auto psu0 = "/com/example/inventory/psu0";
251     extVersion = "manufacturer=TestManu,model=DifferentModel";
252     activation = std::make_unique<Activation>(
253         mockedBus, dBusPath, versionId, extVersion, status, associations,
254         filePath, &mockedAssociationInterface, &mockedActivationListener);
255     ON_CALL(mockedUtils, getPSUInventoryPath(_))
256         .WillByDefault(Return(std::vector<std::string>({psu0})));
257     activation->requestedActivation(RequestedStatus::Active);
258 
259     EXPECT_EQ(Status::Ready, activation->activation());
260 }
261 
262 TEST_F(TestActivation, doUpdateOnePSUManufactureNotCompatible)
263 {
264     constexpr auto psu0 = "/com/example/inventory/psu0";
265     extVersion = "manufacturer=DifferentManu,model=TestModel";
266     activation = std::make_unique<Activation>(
267         mockedBus, dBusPath, versionId, extVersion, status, associations,
268         filePath, &mockedAssociationInterface, &mockedActivationListener);
269     ON_CALL(mockedUtils, getPSUInventoryPath(_))
270         .WillByDefault(Return(std::vector<std::string>({psu0})));
271     activation->requestedActivation(RequestedStatus::Active);
272 
273     EXPECT_EQ(Status::Ready, activation->activation());
274 }
275 
276 TEST_F(TestActivation, doUpdateOnePSUSelfManufactureIsEmpty)
277 {
278     ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(MANUFACTURER)))
279         .WillByDefault(Return(any(PropertyType(std::string("")))));
280     extVersion = "manufacturer=AnyManu,model=TestModel";
281     // Below is the same as doUpdateOnePSUOK case
282     constexpr auto psu0 = "/com/example/inventory/psu0";
283     activation = std::make_unique<Activation>(
284         mockedBus, dBusPath, versionId, extVersion, status, associations,
285         filePath, &mockedAssociationInterface, &mockedActivationListener);
286     ON_CALL(mockedUtils, getPSUInventoryPath(_))
287         .WillByDefault(
288             Return(std::vector<std::string>({psu0}))); // One PSU inventory
289     activation->requestedActivation(RequestedStatus::Active);
290 
291     EXPECT_EQ(Status::Activating, activation->activation());
292 
293     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
294         .Times(1);
295     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
296         .Times(1);
297     onUpdateDone();
298     EXPECT_EQ(Status::Active, activation->activation());
299 }
300 
301 TEST_F(TestActivation, doUpdateFourPSUsSecondPSUNotCompatible)
302 {
303     constexpr auto psu0 = "/com/example/inventory/psu0";
304     constexpr auto psu1 = "/com/example/inventory/psu1";
305     constexpr auto psu2 = "/com/example/inventory/psu2";
306     constexpr auto psu3 = "/com/example/inventory/psu3";
307     ON_CALL(mockedUtils, getPropertyImpl(_, _, StrEq(psu1), _, StrEq(MODEL)))
308         .WillByDefault(
309             Return(any(PropertyType(std::string("DifferentModel")))));
310     activation = std::make_unique<Activation>(
311         mockedBus, dBusPath, versionId, extVersion, status, associations,
312         filePath, &mockedAssociationInterface, &mockedActivationListener);
313     ON_CALL(mockedUtils, getPSUInventoryPath(_))
314         .WillByDefault(Return(
315             std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs
316     activation->requestedActivation(RequestedStatus::Active);
317 
318     const auto& psuQueue = getPsuQueue();
319     EXPECT_EQ(3u, psuQueue.size());
320 
321     // Only 3 PSUs shall be updated, and psu1 shall be skipped
322     EXPECT_EQ(Status::Activating, activation->activation());
323     EXPECT_EQ(10, getProgress());
324 
325     onUpdateDone();
326     EXPECT_EQ(Status::Activating, activation->activation());
327     EXPECT_EQ(36, getProgress());
328 
329     onUpdateDone();
330     EXPECT_EQ(Status::Activating, activation->activation());
331     EXPECT_EQ(62, getProgress());
332 
333     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
334         .Times(1);
335     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
336         .Times(1);
337 
338     onUpdateDone();
339     EXPECT_EQ(Status::Active, activation->activation());
340 }
341 
342 TEST_F(TestActivation, doUpdateWhenNoFilePathInActiveState)
343 {
344     filePath = "";
345     status = Status::Active; // Typically, a running PSU software is active
346                              // without file path
347     constexpr auto psu0 = "/com/example/inventory/psu0";
348     activation = std::make_unique<Activation>(
349         mockedBus, dBusPath, versionId, extVersion, status, associations,
350         filePath, &mockedAssociationInterface, &mockedActivationListener);
351 
352     ON_CALL(mockedUtils, getPSUInventoryPath(_))
353         .WillByDefault(
354             Return(std::vector<std::string>({psu0}))); // One PSU inventory
355 
356     // There shall be no DBus call to start update service
357     EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _,
358                                                           StrEq("StartUnit")))
359         .Times(0);
360 
361     activation->requestedActivation(RequestedStatus::Active);
362     EXPECT_EQ(Status::Active, activation->activation());
363 }
364 
365 TEST_F(TestActivation, doUpdateWhenNoFilePathInReadyState)
366 {
367     filePath = "";
368     status = Status::Ready; // Usually a Ready activation should have file path,
369                             // but we are testing this case as well
370     constexpr auto psu0 = "/com/example/inventory/psu0";
371     activation = std::make_unique<Activation>(
372         mockedBus, dBusPath, versionId, extVersion, status, associations,
373         filePath, &mockedAssociationInterface, &mockedActivationListener);
374 
375     ON_CALL(mockedUtils, getPSUInventoryPath(_))
376         .WillByDefault(
377             Return(std::vector<std::string>({psu0}))); // One PSU inventory
378 
379     // There shall be no DBus call to start update service
380     EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _,
381                                                           StrEq("StartUnit")))
382         .Times(0);
383 
384     activation->requestedActivation(RequestedStatus::Active);
385     EXPECT_EQ(Status::Ready, activation->activation());
386 }
387 
388 TEST_F(TestActivation, doUpdateWhenPSUIsAssociated)
389 {
390     constexpr auto psu0 = "/com/example/inventory/psu0";
391     status = Status::Active; // Typically, a running PSU software is associated
392     activation = std::make_unique<Activation>(
393         mockedBus, dBusPath, versionId, extVersion, status, associations,
394         filePath, &mockedAssociationInterface, &mockedActivationListener);
395 
396     ON_CALL(mockedUtils, getPSUInventoryPath(_))
397         .WillByDefault(
398             Return(std::vector<std::string>({psu0}))); // One PSU inventory
399 
400     // When PSU is already associated, there shall be no DBus call to start
401     // update service
402     EXPECT_CALL(mockedUtils, isAssociated(StrEq(psu0), _))
403         .WillOnce(Return(true));
404     EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _,
405                                                           StrEq("StartUnit")))
406         .Times(0);
407 
408     activation->requestedActivation(RequestedStatus::Active);
409     EXPECT_EQ(Status::Active, activation->activation());
410 }
411