1 #include "src/associations.hpp"
2 
3 #include "src/test/util/asio_server_class.hpp"
4 #include "src/test/util/association_objects.hpp"
5 #include "src/test/util/debug_output.hpp"
6 
7 #include <sdbusplus/asio/connection.hpp>
8 #include <sdbusplus/asio/object_server.hpp>
9 
10 #include <gtest/gtest.h>
11 
12 class TestAssociations : public AsioServerClassTest
13 {};
14 sdbusplus::asio::object_server* TestAssociations::AsioServerClassTest::server =
15     nullptr;
16 
17 // Verify call when path is not in associated owners
18 TEST_F(TestAssociations, SourcePathNotInAssociations)
19 {
20     EXPECT_NE(nullptr, server);
21     std::string sourcePath = "/xyz/openbmc_project/no/association";
22     AssociationMaps assocMaps;
23 
24     removeAssociation(sourcePath, defaultDbusSvc, *server, assocMaps);
25 }
26 
27 // Verify call when owner is not in associated owners
28 TEST_F(TestAssociations, OwnerNotInAssociations)
29 {
30     AssociationMaps assocMaps;
31     assocMaps.owners = createDefaultOwnerAssociation();
32 
33     removeAssociation(defaultSourcePath, defaultDbusSvc, *server, assocMaps);
34 }
35 
36 // Verify call when path is not in associated interfaces
37 TEST_F(TestAssociations, PathNotInAssocInterfaces)
38 {
39     AssociationMaps assocMaps;
40 
41     assocMaps.owners = createDefaultOwnerAssociation();
42 
43     removeAssociation(defaultSourcePath, defaultDbusSvc, *server, assocMaps);
44 
45     EXPECT_TRUE(assocMaps.owners.empty());
46 }
47 
48 // Verify call when path is in associated interfaces
49 TEST_F(TestAssociations, PathIsInAssociatedInterfaces)
50 {
51     // Build up these objects so that an associated interface will match
52     // with the associated owner being removed
53     AssociationMaps assocMaps;
54     assocMaps.owners = createDefaultOwnerAssociation();
55     assocMaps.ifaces = createDefaultInterfaceAssociation(server);
56 
57     removeAssociation(defaultSourcePath, defaultDbusSvc, *server, assocMaps);
58 
59     // Verify owner association was deleted
60     EXPECT_TRUE(assocMaps.owners.empty());
61 
62     // Verify endpoint was deleted from interface association
63     auto intfEndpoints =
64         std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]);
65     EXPECT_EQ(intfEndpoints.size(), 0);
66     intfEndpoints = std::get<endpointsPos>(assocMaps.ifaces[defaultRevPath]);
67     EXPECT_EQ(intfEndpoints.size(), 0);
68 }
69 
70 // Verify call when path is in associated interfaces, with extra endpoints
71 TEST_F(TestAssociations, PathIsInAssociatedInterfacesExtraEndpoints)
72 {
73     // Build up these objects so that an associated interface will match
74     // with the associated owner being removed
75     AssociationMaps assocMaps;
76     assocMaps.owners = createDefaultOwnerAssociation();
77     assocMaps.ifaces = createDefaultInterfaceAssociation(server);
78 
79     // Add another endpoint to the assoc interfaces
80     addEndpointToInterfaceAssociation(assocMaps.ifaces);
81 
82     removeAssociation(defaultSourcePath, defaultDbusSvc, *server, assocMaps);
83 
84     // Verify owner association was deleted
85     EXPECT_TRUE(assocMaps.owners.empty());
86 
87     // Verify all endpoints are deleted since source path was deleted
88     auto intfEndpoints =
89         std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]);
90     EXPECT_EQ(intfEndpoints.size(), 0);
91     intfEndpoints = std::get<endpointsPos>(assocMaps.ifaces[defaultRevPath]);
92     EXPECT_EQ(intfEndpoints.size(), 0);
93 }
94 
95 // Verify no associations or endpoints removed when the change is identical
96 TEST_F(TestAssociations, checkAssociationEndpointRemovesNoEpRemove)
97 {
98     AssociationPaths newAssocPaths = {{defaultFwdPath, {defaultEndpoint}},
99                                       {defaultRevPath, {defaultSourcePath}}};
100 
101     AssociationMaps assocMaps;
102     assocMaps.owners = createDefaultOwnerAssociation();
103     assocMaps.ifaces = createDefaultInterfaceAssociation(server);
104 
105     checkAssociationEndpointRemoves(defaultSourcePath, defaultDbusSvc,
106                                     newAssocPaths, *server, assocMaps);
107 
108     // Verify endpoints were not deleted because they matche with what was
109     // in the original
110     auto intfEndpoints =
111         std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]);
112     EXPECT_EQ(intfEndpoints.size(), 1);
113     intfEndpoints = std::get<endpointsPos>(assocMaps.ifaces[defaultRevPath]);
114     EXPECT_EQ(intfEndpoints.size(), 1);
115 }
116 
117 // Verify endpoint is removed when assoc path is different
118 TEST_F(TestAssociations, checkAssociationEndpointRemovesEpRemoveApDiff)
119 {
120     AssociationPaths newAssocPaths = {{"/different/path", {defaultEndpoint}}};
121 
122     AssociationMaps assocMaps;
123     assocMaps.owners = createDefaultOwnerAssociation();
124     assocMaps.ifaces = createDefaultInterfaceAssociation(server);
125 
126     checkAssociationEndpointRemoves(defaultSourcePath, defaultDbusSvc,
127                                     newAssocPaths, *server, assocMaps);
128 
129     // Verify initial endpoints were deleted because the new path
130     auto intfEndpoints =
131         std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]);
132     EXPECT_EQ(intfEndpoints.size(), 0);
133     intfEndpoints = std::get<endpointsPos>(assocMaps.ifaces[defaultRevPath]);
134     EXPECT_EQ(intfEndpoints.size(), 0);
135 }
136 
137 // Verify endpoint is removed when endpoint is different
138 TEST_F(TestAssociations, checkAssociationEndpointRemovesEpRemoveEpChanged)
139 {
140     AssociationPaths newAssocPaths = {
141         {defaultFwdPath, {defaultEndpoint + "/different"}},
142         {defaultRevPath, {defaultSourcePath + "/different"}}};
143 
144     AssociationMaps assocMaps;
145     assocMaps.owners = createDefaultOwnerAssociation();
146     assocMaps.ifaces = createDefaultInterfaceAssociation(server);
147 
148     checkAssociationEndpointRemoves(defaultSourcePath, defaultDbusSvc,
149                                     newAssocPaths, *server, assocMaps);
150 
151     // Verify initial endpoints were deleted because of different endpoints
152     auto intfEndpoints =
153         std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]);
154     EXPECT_EQ(intfEndpoints.size(), 0);
155     intfEndpoints = std::get<endpointsPos>(assocMaps.ifaces[defaultRevPath]);
156     EXPECT_EQ(intfEndpoints.size(), 0);
157 }
158 
159 // Verify existing endpoint deleted when empty endpoint is provided
160 TEST_F(TestAssociations, associationChangedEmptyEndpoint)
161 {
162     std::vector<Association> associations = {
163         {"inventory_cee", "error_cee", ""}};
164     InterfaceMapType interfaceMap;
165 
166     AssociationMaps assocMaps;
167     assocMaps.owners = createDefaultOwnerAssociation();
168     assocMaps.ifaces = createDefaultInterfaceAssociation(server);
169 
170     // Empty endpoint will result in deletion of corresponding assocInterface
171     associationChanged(*server, associations, defaultSourcePath, defaultDbusSvc,
172                        interfaceMap, assocMaps);
173 
174     // Both of these should be 0 since we have an invalid endpoint
175     auto intfEndpoints =
176         std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]);
177     EXPECT_EQ(intfEndpoints.size(), 0);
178     intfEndpoints = std::get<endpointsPos>(assocMaps.ifaces[defaultRevPath]);
179     EXPECT_EQ(intfEndpoints.size(), 0);
180 
181     EXPECT_EQ(assocMaps.pending.size(), 0);
182 }
183 
184 // Add a new association with endpoint
185 TEST_F(TestAssociations, associationChangedAddNewAssoc)
186 {
187     std::vector<Association> associations = {
188         {"abc", "def", "/xyz/openbmc_project/new/endpoint"}};
189 
190     AssociationMaps assocMaps;
191     assocMaps.owners = createDefaultOwnerAssociation();
192     assocMaps.ifaces = createDefaultInterfaceAssociation(server);
193 
194     // Make it look like the assoc endpoints are on D-Bus
195     InterfaceMapType interfaceMap = {
196         {"/new/source/path", {{defaultDbusSvc, {"a"}}}},
197         {"/xyz/openbmc_project/new/endpoint", {{defaultDbusSvc, {"a"}}}}};
198 
199     associationChanged(*server, associations, "/new/source/path",
200                        defaultDbusSvc, interfaceMap, assocMaps);
201 
202     // Two source paths
203     EXPECT_EQ(assocMaps.owners.size(), 2);
204 
205     // Four interfaces
206     EXPECT_EQ(assocMaps.ifaces.size(), 4);
207 
208     // Nothing pending
209     EXPECT_EQ(assocMaps.pending.size(), 0);
210 
211     // New endpoint so assocMaps.ifaces should be same size
212     auto intfEndpoints =
213         std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]);
214     EXPECT_EQ(intfEndpoints.size(), 1);
215 }
216 
217 // Add a new association to empty objects
218 TEST_F(TestAssociations, associationChangedAddNewAssocEmptyObj)
219 {
220     std::string sourcePath = "/logging/entry/1";
221     std::string owner = "xyz.openbmc_project.Test";
222     std::vector<Association> associations = {
223         {"inventory_canaeo", "error_canaeo",
224          "/xyz/openbmc_project/inventory/system/chassis"}};
225 
226     // Empty objects because this test will ensure assocOwners adds the
227     // changed association and interface
228     AssociationMaps assocMaps;
229 
230     // Make it look like the assoc endpoints are on D-Bus
231     InterfaceMapType interfaceMap = createDefaultInterfaceMap();
232 
233     associationChanged(*server, associations, defaultSourcePath, defaultDbusSvc,
234                        interfaceMap, assocMaps);
235 
236     // New associations so ensure it now contains a single entry
237     EXPECT_EQ(assocMaps.owners.size(), 1);
238 
239     // Nothing pending
240     EXPECT_EQ(assocMaps.pending.size(), 0);
241 
242     // Verify corresponding assoc paths each have one endpoint in assoc
243     // interfaces and that those endpoints match
244     auto singleOwner = assocMaps.owners[defaultSourcePath];
245     auto singleIntf = singleOwner[defaultDbusSvc];
246     for (auto i : singleIntf)
247     {
248         auto intfEndpoints = std::get<endpointsPos>(assocMaps.ifaces[i.first]);
249         EXPECT_EQ(intfEndpoints.size(), 1);
250         EXPECT_EQ(intfEndpoints[0], *i.second.begin());
251     }
252 }
253 
254 // Add a new association to same source path but with new owner
255 TEST_F(TestAssociations, associationChangedAddNewAssocNewOwner)
256 {
257     std::string newOwner = "xyz.openbmc_project.Test2";
258     std::vector<Association> associations = {
259         {"inventory_canano", "error_canano",
260          "/xyz/openbmc_project/inventory/system/chassis"}};
261 
262     // Make it look like the assoc endpoints are on D-Bus
263     InterfaceMapType interfaceMap = createDefaultInterfaceMap();
264 
265     AssociationMaps assocMaps;
266     assocMaps.owners = createDefaultOwnerAssociation();
267     assocMaps.ifaces = createDefaultInterfaceAssociation(server);
268 
269     associationChanged(*server, associations, defaultSourcePath, newOwner,
270                        interfaceMap, assocMaps);
271 
272     // New endpoint so assocOwners should be same size
273     EXPECT_EQ(assocMaps.owners.size(), 1);
274 
275     // Ensure only one endpoint under first path
276     auto intfEndpoints =
277         std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]);
278     EXPECT_EQ(intfEndpoints.size(), 1);
279 
280     // Ensure the 2 new association endpoints are under the new owner
281     auto a = assocMaps.owners.find(defaultSourcePath);
282     auto o = a->second.find(newOwner);
283     EXPECT_EQ(o->second.size(), 2);
284 
285     // Nothing pending
286     EXPECT_EQ(assocMaps.pending.size(), 0);
287 }
288 
289 // Add a new association to existing interface path
290 TEST_F(TestAssociations, associationChangedAddNewAssocSameInterface)
291 {
292     std::vector<Association> associations = {
293         {"abc", "error", "/xyz/openbmc_project/inventory/system/chassis"}};
294 
295     // Make it look like the assoc endpoints are on D-Bus
296     InterfaceMapType interfaceMap = createDefaultInterfaceMap();
297 
298     AssociationMaps assocMaps;
299     assocMaps.owners = createDefaultOwnerAssociation();
300     assocMaps.ifaces = createDefaultInterfaceAssociation(server);
301 
302     associationChanged(*server, associations, defaultSourcePath, defaultDbusSvc,
303                        interfaceMap, assocMaps);
304 
305     // Should have 3 entries in AssociationInterfaces, one is just missing an
306     // endpoint
307     EXPECT_EQ(assocMaps.ifaces.size(), 3);
308 
309     // Change to existing interface so it will be removed here
310     auto intfEndpoints =
311         std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]);
312     EXPECT_EQ(intfEndpoints.size(), 0);
313 
314     // The new endpoint should exist though in it's place
315     intfEndpoints = std::get<endpointsPos>(
316         assocMaps.ifaces[defaultSourcePath + "/" + "abc"]);
317     EXPECT_EQ(intfEndpoints.size(), 1);
318 
319     // Added to an existing owner path so still 1
320     EXPECT_EQ(assocMaps.owners.size(), 1);
321 
322     EXPECT_EQ(assocMaps.pending.size(), 0);
323 }
324 
325 // Add 2 pending associations
326 TEST_F(TestAssociations, addPendingAssocs)
327 {
328     AssociationMaps assocMaps;
329 
330     addPendingAssociation(defaultSourcePath, "inventory", defaultEndpoint,
331                           "error", defaultDbusSvc, assocMaps);
332 
333     EXPECT_TRUE(assocMaps.ifaces.empty());
334     EXPECT_TRUE(assocMaps.owners.empty());
335 
336     EXPECT_EQ(assocMaps.pending.size(), 1);
337 
338     addPendingAssociation("some/other/path", "inventory", defaultEndpoint,
339                           "error", defaultDbusSvc, assocMaps);
340 
341     EXPECT_TRUE(assocMaps.ifaces.empty());
342     EXPECT_TRUE(assocMaps.owners.empty());
343 
344     EXPECT_EQ(assocMaps.pending.size(), 2);
345 }
346 
347 // Test adding a new endpoint to a pending association
348 TEST_F(TestAssociations, addPendingAssocsNewEndpoints)
349 {
350     AssociationMaps assocMaps;
351 
352     addPendingAssociation(defaultSourcePath, "inventory", defaultEndpoint,
353                           "error", defaultDbusSvc, assocMaps);
354 
355     EXPECT_EQ(assocMaps.pending.size(), 1);
356 
357     addPendingAssociation(defaultSourcePath, "inventory", "some/other/endpoint",
358                           "error", defaultDbusSvc, assocMaps);
359 
360     // Same pending path, so still just 1 entry
361     EXPECT_EQ(assocMaps.pending.size(), 1);
362 
363     auto assoc = assocMaps.pending.find(defaultSourcePath);
364     EXPECT_NE(assoc, assocMaps.pending.end());
365 
366     auto& endpoints = assoc->second;
367     EXPECT_EQ(endpoints.size(), 2);
368 }
369 
370 // Test adding a new owner to a pending association
371 TEST_F(TestAssociations, addPendingAssocsNewOwner)
372 {
373     AssociationMaps assocMaps;
374 
375     addPendingAssociation(defaultSourcePath, "inventory", defaultEndpoint,
376                           "error", defaultDbusSvc, assocMaps);
377 
378     EXPECT_EQ(assocMaps.pending.size(), 1);
379 
380     addPendingAssociation(defaultSourcePath, "inventory", defaultEndpoint,
381                           "error", "new owner", assocMaps);
382 
383     EXPECT_EQ(assocMaps.pending.size(), 1);
384 
385     auto assoc = assocMaps.pending.find(defaultSourcePath);
386     EXPECT_NE(assoc, assocMaps.pending.end());
387 
388     auto& endpoints = assoc->second;
389     EXPECT_EQ(endpoints.size(), 2);
390 }
391 
392 // Add a pending association inside associationChanged
393 TEST_F(TestAssociations, associationChangedPending)
394 {
395     std::vector<Association> associations = {
396         {"abc", "def", "/xyz/openbmc_project/new/endpoint"}};
397 
398     AssociationMaps assocMaps;
399     InterfaceMapType interfaceMap;
400 
401     associationChanged(*server, associations, "/new/source/path",
402                        defaultDbusSvc, interfaceMap, assocMaps);
403 
404     // No associations were actually added
405     EXPECT_EQ(assocMaps.owners.size(), 0);
406     EXPECT_EQ(assocMaps.ifaces.size(), 0);
407 
408     // 1 pending association
409     EXPECT_EQ(assocMaps.pending.size(), 1);
410 }
411 
412 // Test removing pending associations
413 TEST_F(TestAssociations, testRemoveFromPendingAssociations)
414 {
415     AssociationMaps assocMaps;
416 
417     addPendingAssociation(defaultSourcePath, "inventory", defaultEndpoint,
418                           "error", defaultDbusSvc, assocMaps);
419 
420     addPendingAssociation(defaultSourcePath, "inventory", "some/other/endpoint",
421                           "error", defaultDbusSvc, assocMaps);
422 
423     EXPECT_EQ(assocMaps.pending.size(), 1);
424 
425     removeFromPendingAssociations("some/other/endpoint", assocMaps);
426 
427     // Still 1 pending entry, but down to 1 endpoint
428     EXPECT_EQ(assocMaps.pending.size(), 1);
429 
430     auto assoc = assocMaps.pending.find(defaultSourcePath);
431     EXPECT_NE(assoc, assocMaps.pending.end());
432     auto& endpoints = assoc->second;
433     EXPECT_EQ(endpoints.size(), 1);
434 
435     // Now nothing pending
436     removeFromPendingAssociations(defaultEndpoint, assocMaps);
437     EXPECT_EQ(assocMaps.pending.size(), 0);
438 }
439 
440 // Test moving a pending association to a real one
441 TEST_F(TestAssociations, checkIfPending)
442 {
443     AssociationMaps assocMaps;
444     InterfaceMapType interfaceMap = {
445         {defaultSourcePath, {{defaultDbusSvc, {"a"}}}},
446         {defaultEndpoint, {{defaultDbusSvc, {"b"}}}}};
447 
448     addPendingAssociation(defaultSourcePath, "inventory_cip", defaultEndpoint,
449                           "error_cip", defaultDbusSvc, assocMaps);
450     EXPECT_EQ(assocMaps.pending.size(), 1);
451 
452     // Move the pending association to a real association
453     checkIfPendingAssociation(defaultSourcePath, interfaceMap, assocMaps,
454                               *server);
455 
456     EXPECT_TRUE(assocMaps.pending.empty());
457     EXPECT_EQ(assocMaps.owners.size(), 1);
458     EXPECT_EQ(assocMaps.ifaces.size(), 2);
459 
460     // This shouldn't do anything, since /new/path isn't pending
461     checkIfPendingAssociation("/new/path", interfaceMap, assocMaps, *server);
462     EXPECT_TRUE(assocMaps.pending.empty());
463     EXPECT_EQ(assocMaps.owners.size(), 1);
464     EXPECT_EQ(assocMaps.ifaces.size(), 2);
465 }
466 
467 TEST_F(TestAssociations, findAssociations)
468 {
469     std::vector<std::tuple<std::string, Association>> associationData;
470     AssociationMaps assocMaps;
471 
472     assocMaps.owners = {
473         {"pathA",
474          {{"ownerA",
475            {{"pathA/typeA", {"endpointA", "endpointB"}},
476             {"endpointA/type0", {"pathA"}}}}}},
477 
478         {"pathJ",
479          {{"ownerC",
480            {{"pathJ/typeA", {"endpointF"}}, {"endpointF/type0", {"pathJ"}}}}}},
481 
482         {"pathX",
483          {{"ownerB",
484            {{"pathX/typeB", {"endpointA"}}, {"endpointA/type1", {"pathX"}}}}}}};
485 
486     findAssociations("endpointA", assocMaps, associationData);
487     ASSERT_EQ(associationData.size(), 2);
488 
489     {
490         auto ad = std::find_if(
491             associationData.begin(), associationData.end(),
492             [](const auto& ad) { return std::get<0>(ad) == "ownerA"; });
493         ASSERT_NE(ad, associationData.end());
494 
495         auto& a = std::get<1>(*ad);
496         ASSERT_EQ(std::get<0>(a), "type0");
497         ASSERT_EQ(std::get<1>(a), "typeA");
498         ASSERT_EQ(std::get<2>(a), "pathA");
499     }
500     {
501         auto ad = std::find_if(
502             associationData.begin(), associationData.end(),
503             [](const auto& ad) { return std::get<0>(ad) == "ownerB"; });
504         ASSERT_NE(ad, associationData.end());
505 
506         auto& a = std::get<1>(*ad);
507         ASSERT_EQ(std::get<0>(a), "type1");
508         ASSERT_EQ(std::get<1>(a), "typeB");
509         ASSERT_EQ(std::get<2>(a), "pathX");
510     }
511 }
512 
513 TEST_F(TestAssociations, moveAssocToPendingNoOp)
514 {
515     AssociationMaps assocMaps;
516 
517     // Not an association, so it shouldn't do anything
518     moveAssociationToPending(defaultEndpoint, assocMaps, *server);
519 
520     EXPECT_TRUE(assocMaps.pending.empty());
521     EXPECT_TRUE(assocMaps.owners.empty());
522     EXPECT_TRUE(assocMaps.ifaces.empty());
523 }
524 
525 TEST_F(TestAssociations, moveAssocToPending)
526 {
527     AssociationMaps assocMaps;
528     assocMaps.owners = createDefaultOwnerAssociation();
529     assocMaps.ifaces = createDefaultInterfaceAssociation(server);
530 
531     moveAssociationToPending(defaultEndpoint, assocMaps, *server);
532 
533     // Check it's now pending
534     EXPECT_EQ(assocMaps.pending.size(), 1);
535     EXPECT_EQ(assocMaps.pending.begin()->first, defaultEndpoint);
536 
537     // No more assoc owners
538     EXPECT_TRUE(assocMaps.owners.empty());
539 
540     // Check the association interfaces were removed
541     {
542         auto assocs = assocMaps.ifaces.find(defaultFwdPath);
543         auto& iface = std::get<ifacePos>(assocs->second);
544         auto& endpoints = std::get<endpointsPos>(assocs->second);
545 
546         EXPECT_EQ(iface.get(), nullptr);
547         EXPECT_TRUE(endpoints.empty());
548     }
549     {
550         auto assocs = assocMaps.ifaces.find(defaultRevPath);
551         auto& iface = std::get<ifacePos>(assocs->second);
552         auto& endpoints = std::get<endpointsPos>(assocs->second);
553 
554         EXPECT_EQ(iface.get(), nullptr);
555         EXPECT_TRUE(endpoints.empty());
556     }
557 }
558