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