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::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_t 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(mockedAssociationInterface, addUpdateableAssociation(dBusPath))
122         .Times(0);
123     EXPECT_CALL(mockedActivationListener, onUpdateDone(_, _)).Times(0);
124     EXPECT_EQ(Status::Failed, activation->activation());
125 }
126 
127 TEST_F(TestActivation, doUpdateOnePSUOK)
128 {
129     constexpr auto psu0 = "/com/example/inventory/psu0";
130     activation = std::make_unique<Activation>(
131         mockedBus, dBusPath, versionId, extVersion, status, associations,
132         filePath, &mockedAssociationInterface, &mockedActivationListener);
133     ON_CALL(mockedUtils, getPSUInventoryPath(_))
134         .WillByDefault(
135             Return(std::vector<std::string>({psu0}))); // One PSU inventory
136     activation->requestedActivation(RequestedStatus::Active);
137 
138     EXPECT_EQ(Status::Activating, activation->activation());
139 
140     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
141         .Times(1);
142     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
143         .Times(1);
144     EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath))
145         .Times(1);
146     EXPECT_CALL(mockedActivationListener,
147                 onUpdateDone(StrEq(versionId), StrEq(psu0)))
148         .Times(1);
149     onUpdateDone();
150     EXPECT_EQ(Status::Active, activation->activation());
151 }
152 
153 TEST_F(TestActivation, doUpdateFourPSUsOK)
154 {
155     constexpr auto psu0 = "/com/example/inventory/psu0";
156     constexpr auto psu1 = "/com/example/inventory/psu1";
157     constexpr auto psu2 = "/com/example/inventory/psu2";
158     constexpr auto psu3 = "/com/example/inventory/psu3";
159     activation = std::make_unique<Activation>(
160         mockedBus, dBusPath, versionId, extVersion, status, associations,
161         filePath, &mockedAssociationInterface, &mockedActivationListener);
162     ON_CALL(mockedUtils, getPSUInventoryPath(_))
163         .WillByDefault(Return(
164             std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs
165     activation->requestedActivation(RequestedStatus::Active);
166 
167     EXPECT_EQ(Status::Activating, activation->activation());
168     EXPECT_EQ(10, getProgress());
169 
170     EXPECT_CALL(mockedActivationListener,
171                 onUpdateDone(StrEq(versionId), StrEq(psu0)))
172         .Times(1);
173     onUpdateDone();
174     EXPECT_EQ(Status::Activating, activation->activation());
175     EXPECT_EQ(30, getProgress());
176 
177     EXPECT_CALL(mockedActivationListener,
178                 onUpdateDone(StrEq(versionId), StrEq(psu1)))
179         .Times(1);
180     onUpdateDone();
181     EXPECT_EQ(Status::Activating, activation->activation());
182     EXPECT_EQ(50, getProgress());
183 
184     EXPECT_CALL(mockedActivationListener,
185                 onUpdateDone(StrEq(versionId), StrEq(psu2)))
186         .Times(1);
187     onUpdateDone();
188     EXPECT_EQ(Status::Activating, activation->activation());
189     EXPECT_EQ(70, getProgress());
190 
191     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
192         .Times(1);
193     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
194         .Times(1);
195     EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath))
196         .Times(1);
197 
198     EXPECT_CALL(mockedActivationListener,
199                 onUpdateDone(StrEq(versionId), StrEq(psu3)))
200         .Times(1);
201     onUpdateDone();
202     EXPECT_EQ(Status::Active, activation->activation());
203 }
204 
205 TEST_F(TestActivation, doUpdateFourPSUsFailonSecond)
206 {
207     constexpr auto psu0 = "/com/example/inventory/psu0";
208     constexpr auto psu1 = "/com/example/inventory/psu1";
209     constexpr auto psu2 = "/com/example/inventory/psu2";
210     constexpr auto psu3 = "/com/example/inventory/psu3";
211     activation = std::make_unique<Activation>(
212         mockedBus, dBusPath, versionId, extVersion, status, associations,
213         filePath, &mockedAssociationInterface, &mockedActivationListener);
214     ON_CALL(mockedUtils, getPSUInventoryPath(_))
215         .WillByDefault(Return(
216             std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs
217     activation->requestedActivation(RequestedStatus::Active);
218 
219     EXPECT_EQ(Status::Activating, activation->activation());
220     EXPECT_EQ(10, getProgress());
221 
222     EXPECT_CALL(mockedActivationListener,
223                 onUpdateDone(StrEq(versionId), StrEq(psu0)))
224         .Times(1);
225     onUpdateDone();
226     EXPECT_EQ(Status::Activating, activation->activation());
227     EXPECT_EQ(30, getProgress());
228 
229     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
230         .Times(0);
231     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
232         .Times(0);
233     EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath))
234         .Times(0);
235     EXPECT_CALL(mockedActivationListener, onUpdateDone(_, _)).Times(0);
236     onUpdateFailed();
237     EXPECT_EQ(Status::Failed, activation->activation());
238 }
239 
240 TEST_F(TestActivation, doUpdateOnExceptionFromDbus)
241 {
242     constexpr auto psu0 = "/com/example/inventory/psu0";
243     activation = std::make_unique<Activation>(
244         mockedBus, dBusPath, versionId, extVersion, status, associations,
245         filePath, &mockedAssociationInterface, &mockedActivationListener);
246     ON_CALL(mockedUtils, getPSUInventoryPath(_))
247         .WillByDefault(
248             Return(std::vector<std::string>({psu0}))); // One PSU inventory
249     ON_CALL(sdbusMock, sd_bus_call(_, _, _, _, nullptr))
250         .WillByDefault(Return(-1)); // Make sdbus call failure
251     activation->requestedActivation(RequestedStatus::Active);
252 
253     EXPECT_EQ(Status::Failed, activation->activation());
254 }
255 
256 TEST_F(TestActivation, doUpdateOnePSUModelNotCompatible)
257 {
258     constexpr auto psu0 = "/com/example/inventory/psu0";
259     extVersion = "manufacturer=TestManu,model=DifferentModel";
260     activation = std::make_unique<Activation>(
261         mockedBus, dBusPath, versionId, extVersion, status, associations,
262         filePath, &mockedAssociationInterface, &mockedActivationListener);
263     ON_CALL(mockedUtils, getPSUInventoryPath(_))
264         .WillByDefault(Return(std::vector<std::string>({psu0})));
265     activation->requestedActivation(RequestedStatus::Active);
266 
267     EXPECT_EQ(Status::Ready, activation->activation());
268 }
269 
270 TEST_F(TestActivation, doUpdateOnePSUManufactureNotCompatible)
271 {
272     constexpr auto psu0 = "/com/example/inventory/psu0";
273     extVersion = "manufacturer=DifferentManu,model=TestModel";
274     activation = std::make_unique<Activation>(
275         mockedBus, dBusPath, versionId, extVersion, status, associations,
276         filePath, &mockedAssociationInterface, &mockedActivationListener);
277     ON_CALL(mockedUtils, getPSUInventoryPath(_))
278         .WillByDefault(Return(std::vector<std::string>({psu0})));
279     activation->requestedActivation(RequestedStatus::Active);
280 
281     EXPECT_EQ(Status::Ready, activation->activation());
282 }
283 
284 TEST_F(TestActivation, doUpdateOnePSUSelfManufactureIsEmpty)
285 {
286     ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(MANUFACTURER)))
287         .WillByDefault(Return(any(PropertyType(std::string("")))));
288     extVersion = "manufacturer=AnyManu,model=TestModel";
289     // Below is the same as doUpdateOnePSUOK case
290     constexpr auto psu0 = "/com/example/inventory/psu0";
291     activation = std::make_unique<Activation>(
292         mockedBus, dBusPath, versionId, extVersion, status, associations,
293         filePath, &mockedAssociationInterface, &mockedActivationListener);
294     ON_CALL(mockedUtils, getPSUInventoryPath(_))
295         .WillByDefault(
296             Return(std::vector<std::string>({psu0}))); // One PSU inventory
297     activation->requestedActivation(RequestedStatus::Active);
298 
299     EXPECT_EQ(Status::Activating, activation->activation());
300 
301     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
302         .Times(1);
303     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
304         .Times(1);
305     EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath))
306         .Times(1);
307     onUpdateDone();
308     EXPECT_EQ(Status::Active, activation->activation());
309 }
310 
311 TEST_F(TestActivation, doUpdateFourPSUsSecondPSUNotCompatible)
312 {
313     constexpr auto psu0 = "/com/example/inventory/psu0";
314     constexpr auto psu1 = "/com/example/inventory/psu1";
315     constexpr auto psu2 = "/com/example/inventory/psu2";
316     constexpr auto psu3 = "/com/example/inventory/psu3";
317     ON_CALL(mockedUtils, getPropertyImpl(_, _, StrEq(psu1), _, StrEq(MODEL)))
318         .WillByDefault(
319             Return(any(PropertyType(std::string("DifferentModel")))));
320     activation = std::make_unique<Activation>(
321         mockedBus, dBusPath, versionId, extVersion, status, associations,
322         filePath, &mockedAssociationInterface, &mockedActivationListener);
323     ON_CALL(mockedUtils, getPSUInventoryPath(_))
324         .WillByDefault(Return(
325             std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs
326     activation->requestedActivation(RequestedStatus::Active);
327 
328     const auto& psuQueue = getPsuQueue();
329     EXPECT_EQ(3u, psuQueue.size());
330 
331     // Only 3 PSUs shall be updated, and psu1 shall be skipped
332     EXPECT_EQ(Status::Activating, activation->activation());
333     EXPECT_EQ(10, getProgress());
334 
335     onUpdateDone();
336     EXPECT_EQ(Status::Activating, activation->activation());
337     EXPECT_EQ(36, getProgress());
338 
339     onUpdateDone();
340     EXPECT_EQ(Status::Activating, activation->activation());
341     EXPECT_EQ(62, getProgress());
342 
343     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
344         .Times(1);
345     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
346         .Times(1);
347     EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath))
348         .Times(1);
349 
350     onUpdateDone();
351     EXPECT_EQ(Status::Active, activation->activation());
352 }
353 
354 TEST_F(TestActivation, doUpdateWhenNoFilePathInActiveState)
355 {
356     filePath = "";
357     status = Status::Active; // Typically, a running PSU software is active
358                              // without file path
359     constexpr auto psu0 = "/com/example/inventory/psu0";
360     activation = std::make_unique<Activation>(
361         mockedBus, dBusPath, versionId, extVersion, status, associations,
362         filePath, &mockedAssociationInterface, &mockedActivationListener);
363 
364     ON_CALL(mockedUtils, getPSUInventoryPath(_))
365         .WillByDefault(
366             Return(std::vector<std::string>({psu0}))); // One PSU inventory
367 
368     // There shall be no DBus call to start update service
369     EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _,
370                                                           StrEq("StartUnit")))
371         .Times(0);
372 
373     activation->requestedActivation(RequestedStatus::Active);
374     EXPECT_EQ(Status::Active, activation->activation());
375 }
376 
377 TEST_F(TestActivation, doUpdateWhenNoFilePathInReadyState)
378 {
379     filePath = "";
380     status = Status::Ready; // Usually a Ready activation should have file path,
381                             // but we are testing this case as well
382     constexpr auto psu0 = "/com/example/inventory/psu0";
383     activation = std::make_unique<Activation>(
384         mockedBus, dBusPath, versionId, extVersion, status, associations,
385         filePath, &mockedAssociationInterface, &mockedActivationListener);
386 
387     ON_CALL(mockedUtils, getPSUInventoryPath(_))
388         .WillByDefault(
389             Return(std::vector<std::string>({psu0}))); // One PSU inventory
390 
391     // There shall be no DBus call to start update service
392     EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _,
393                                                           StrEq("StartUnit")))
394         .Times(0);
395 
396     activation->requestedActivation(RequestedStatus::Active);
397     EXPECT_EQ(Status::Ready, activation->activation());
398 }
399 
400 TEST_F(TestActivation, doUpdateWhenPSUIsAssociated)
401 {
402     constexpr auto psu0 = "/com/example/inventory/psu0";
403     status = Status::Active; // Typically, a running PSU software is associated
404     activation = std::make_unique<Activation>(
405         mockedBus, dBusPath, versionId, extVersion, status, associations,
406         filePath, &mockedAssociationInterface, &mockedActivationListener);
407 
408     ON_CALL(mockedUtils, getPSUInventoryPath(_))
409         .WillByDefault(
410             Return(std::vector<std::string>({psu0}))); // One PSU inventory
411 
412     // When PSU is already associated, there shall be no DBus call to start
413     // update service
414     EXPECT_CALL(mockedUtils, isAssociated(StrEq(psu0), _))
415         .WillOnce(Return(true));
416     EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _,
417                                                           StrEq("StartUnit")))
418         .Times(0);
419 
420     activation->requestedActivation(RequestedStatus::Active);
421     EXPECT_EQ(Status::Active, activation->activation());
422 }
423