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