xref: /openbmc/phosphor-objmgr/src/associations.cpp (revision 7dd2029eb1755a5ad59ac33bf462837d7e906dbf)
1 #include "associations.hpp"
2 
3 #include "path.hpp"
4 
5 #include <boost/asio/steady_timer.hpp>
6 #include <sdbusplus/exception.hpp>
7 
8 #include <iostream>
9 #include <string>
10 
updateEndpointsOnDbus(sdbusplus::asio::object_server & objectServer,const std::string & assocPath,AssociationMaps & assocMaps)11 static void updateEndpointsOnDbus(sdbusplus::asio::object_server& objectServer,
12                                   const std::string& assocPath,
13                                   AssociationMaps& assocMaps)
14 {
15     // Don't create an entry in assocMaps.ifaces if not needed.
16     auto iface = assocMaps.ifaces.find(assocPath);
17     if (iface == assocMaps.ifaces.end())
18     {
19         return;
20     }
21     auto& i = std::get<ifacePos>(iface->second);
22     auto& endpoints = std::get<endpointsPos>(iface->second);
23 
24     // If the interface already exists, only need to update
25     // the property value, otherwise create it
26     if (i)
27     {
28         if (endpoints.empty())
29         {
30             objectServer.remove_interface(i);
31             i = nullptr;
32         }
33         else
34         {
35             i->set_property("endpoints", endpoints);
36         }
37     }
38     else if (!endpoints.empty())
39     {
40         i = objectServer.add_interface(assocPath, xyzAssociationInterface);
41         i->register_property("endpoints", endpoints);
42         i->initialize();
43     }
44 
45     if (endpoints.empty())
46     {
47         assocMaps.ifaces.erase(iface);
48     }
49 }
50 
scheduleUpdateEndpointsOnDbus(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,const std::string & assocPath,AssociationMaps & assocMaps)51 static void scheduleUpdateEndpointsOnDbus(
52     boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
53     const std::string& assocPath, AssociationMaps& assocMaps)
54 {
55     static std::set<std::string> delayedUpdatePaths;
56 
57     if (delayedUpdatePaths.contains(assocPath))
58     {
59         return;
60     }
61 
62     auto iface = assocMaps.ifaces.find(assocPath);
63     if (iface == assocMaps.ifaces.end())
64     {
65         return;
66     }
67     auto& endpoints = std::get<endpointsPos>(iface->second);
68 
69     if (endpoints.size() > endpointsCountTimerThreshold)
70     {
71         delayedUpdatePaths.emplace(assocPath);
72         auto timer = std::make_shared<boost::asio::steady_timer>(
73             io, std::chrono::seconds(endpointUpdateDelaySeconds));
74         timer->async_wait([&objectServer, &assocMaps, timer,
75                            assocPath](const boost::system::error_code& ec) {
76             if (!ec)
77             {
78                 updateEndpointsOnDbus(objectServer, assocPath, assocMaps);
79             }
80             delayedUpdatePaths.erase(assocPath);
81         });
82     }
83     else
84     {
85         updateEndpointsOnDbus(objectServer, assocPath, assocMaps);
86     }
87 }
88 
removeAssociation(boost::asio::io_context & io,const std::string & sourcePath,const std::string & owner,sdbusplus::asio::object_server & server,AssociationMaps & assocMaps)89 void removeAssociation(boost::asio::io_context& io,
90                        const std::string& sourcePath, const std::string& owner,
91                        sdbusplus::asio::object_server& server,
92                        AssociationMaps& assocMaps)
93 {
94     // Use associationOwners to find the association paths and endpoints
95     // that the passed in object path and service own.  Remove all of
96     // these endpoints from the actual association D-Bus objects, and if
97     // the endpoints property is then empty, the whole association object
98     // can be removed.  Note there can be multiple services that own an
99     // association, and also that sourcePath is the path of the object
100     // that contains the org.openbmc.Associations interface and not the
101     // association path itself.
102 
103     // Find the services that have associations for this object path
104     auto owners = assocMaps.owners.find(sourcePath);
105     if (owners == assocMaps.owners.end())
106     {
107         return;
108     }
109 
110     // Find the association paths and endpoints owned by this object
111     // path for this service.
112     auto assocs = owners->second.find(owner);
113     if (assocs == owners->second.end())
114     {
115         return;
116     }
117 
118     for (const auto& [assocPath, endpointsToRemove] : assocs->second)
119     {
120         removeAssociationEndpoints(io, server, assocPath, endpointsToRemove,
121                                    assocMaps);
122     }
123 
124     // Remove the associationOwners entries for this owning path/service.
125     owners->second.erase(assocs);
126     if (owners->second.empty())
127     {
128         assocMaps.owners.erase(owners);
129     }
130 
131     // If we were still waiting on the other side of this association to
132     // show up, cancel that wait.
133     removeFromPendingAssociations(sourcePath, assocMaps);
134 }
135 
removeAssociationEndpoints(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,const std::string & assocPath,const boost::container::flat_set<std::string> & endpointsToRemove,AssociationMaps & assocMaps)136 void removeAssociationEndpoints(
137     boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
138     const std::string& assocPath,
139     const boost::container::flat_set<std::string>& endpointsToRemove,
140     AssociationMaps& assocMaps)
141 {
142     auto assoc = assocMaps.ifaces.find(assocPath);
143     if (assoc == assocMaps.ifaces.end())
144     {
145         return;
146     }
147 
148     auto& endpointsInDBus = std::get<endpointsPos>(assoc->second);
149 
150     for (const auto& endpointToRemove : endpointsToRemove)
151     {
152         auto e = std::find(endpointsInDBus.begin(), endpointsInDBus.end(),
153                            endpointToRemove);
154 
155         if (e != endpointsInDBus.end())
156         {
157             endpointsInDBus.erase(e);
158         }
159     }
160 
161     scheduleUpdateEndpointsOnDbus(io, objectServer, assocPath, assocMaps);
162 }
163 
checkAssociationEndpointRemoves(boost::asio::io_context & io,const std::string & sourcePath,const std::string & owner,const AssociationPaths & newAssociations,sdbusplus::asio::object_server & objectServer,AssociationMaps & assocMaps)164 void checkAssociationEndpointRemoves(
165     boost::asio::io_context& io, const std::string& sourcePath,
166     const std::string& owner, const AssociationPaths& newAssociations,
167     sdbusplus::asio::object_server& objectServer, AssociationMaps& assocMaps)
168 {
169     // Find the services that have associations on this path.
170     auto originalOwners = assocMaps.owners.find(sourcePath);
171     if (originalOwners == assocMaps.owners.end())
172     {
173         return;
174     }
175 
176     // Find the associations for this service
177     auto originalAssociations = originalOwners->second.find(owner);
178     if (originalAssociations == originalOwners->second.end())
179     {
180         return;
181     }
182 
183     // Compare the new endpoints versus the original endpoints, and
184     // remove any of the original ones that aren't in the new list.
185     for (const auto& [originalAssocPath, originalEndpoints] :
186          originalAssociations->second)
187     {
188         // Check if this source even still has each association that
189         // was there previously, and if not, remove all of its endpoints
190         // from the D-Bus endpoints property which will cause the whole
191         // association path to be removed if no endpoints remain.
192         auto newEndpoints = newAssociations.find(originalAssocPath);
193         if (newEndpoints == newAssociations.end())
194         {
195             removeAssociationEndpoints(io, objectServer, originalAssocPath,
196                                        originalEndpoints, assocMaps);
197         }
198         else
199         {
200             // The association is still there.  Check if the endpoints
201             // changed.
202             boost::container::flat_set<std::string> toRemove;
203 
204             for (const auto& originalEndpoint : originalEndpoints)
205             {
206                 if (std::find(newEndpoints->second.begin(),
207                               newEndpoints->second.end(), originalEndpoint) ==
208                     newEndpoints->second.end())
209                 {
210                     toRemove.emplace(originalEndpoint);
211                 }
212             }
213             if (!toRemove.empty())
214             {
215                 removeAssociationEndpoints(io, objectServer, originalAssocPath,
216                                            toRemove, assocMaps);
217             }
218         }
219     }
220 }
221 
addEndpointsToAssocIfaces(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,const std::string & assocPath,const boost::container::flat_set<std::string> & endpointPaths,AssociationMaps & assocMaps)222 static void addEndpointsToAssocIfaces(
223     boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
224     const std::string& assocPath,
225     const boost::container::flat_set<std::string>& endpointPaths,
226     AssociationMaps& assocMaps)
227 {
228     auto& iface = assocMaps.ifaces[assocPath];
229     auto& endpoints = std::get<endpointsPos>(iface);
230 
231     // Only add new endpoints
232     for (const auto& e : endpointPaths)
233     {
234         if (std::find(endpoints.begin(), endpoints.end(), e) == endpoints.end())
235         {
236             endpoints.push_back(e);
237         }
238     }
239     scheduleUpdateEndpointsOnDbus(io, objectServer, assocPath, assocMaps);
240 }
241 
associationChanged(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,const std::vector<Association> & associations,const std::string & path,const std::string & owner,const InterfaceMapType & interfaceMap,AssociationMaps & assocMaps)242 void associationChanged(
243     boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
244     const std::vector<Association>& associations, const std::string& path,
245     const std::string& owner, const InterfaceMapType& interfaceMap,
246     AssociationMaps& assocMaps)
247 {
248     AssociationPaths objects;
249 
250     for (const Association& association : associations)
251     {
252         std::string forward;
253         std::string reverse;
254         std::string objectPath;
255         std::tie(forward, reverse, objectPath) = association;
256 
257         if (objectPath.empty())
258         {
259             std::cerr << "Found invalid association on path " << path << "\n";
260             continue;
261         }
262 
263         // Can't create this association if the endpoint isn't on D-Bus.
264         if (!interfaceMap.contains(objectPath))
265         {
266             addPendingAssociation(objectPath, reverse, path, forward, owner,
267                                   assocMaps);
268             continue;
269         }
270 
271         if (!forward.empty())
272         {
273             objects[appendPathSegment(path, forward)].emplace(objectPath);
274         }
275         if (!reverse.empty())
276         {
277             objects[appendPathSegment(objectPath, reverse)].emplace(path);
278         }
279     }
280     for (const auto& object : objects)
281     {
282         addEndpointsToAssocIfaces(io, objectServer, object.first, object.second,
283                                   assocMaps);
284     }
285 
286     // Check for endpoints being removed instead of added
287     checkAssociationEndpointRemoves(io, path, owner, objects, objectServer,
288                                     assocMaps);
289 
290     if (!objects.empty())
291     {
292         // Update associationOwners with the latest info
293         auto a = assocMaps.owners.find(path);
294         if (a != assocMaps.owners.end())
295         {
296             auto o = a->second.find(owner);
297             if (o != a->second.end())
298             {
299                 o->second = std::move(objects);
300             }
301             else
302             {
303                 a->second.emplace(owner, std::move(objects));
304             }
305         }
306         else
307         {
308             boost::container::flat_map<std::string, AssociationPaths> owners;
309             owners.emplace(owner, std::move(objects));
310             assocMaps.owners.emplace(path, owners);
311         }
312     }
313 }
314 
addPendingAssociation(const std::string & objectPath,const std::string & type,const std::string & endpointPath,const std::string & endpointType,const std::string & owner,AssociationMaps & assocMaps)315 void addPendingAssociation(
316     const std::string& objectPath, const std::string& type,
317     const std::string& endpointPath, const std::string& endpointType,
318     const std::string& owner, AssociationMaps& assocMaps)
319 {
320     Association assoc{type, endpointType, endpointPath};
321 
322     auto p = assocMaps.pending.find(objectPath);
323     if (p == assocMaps.pending.end())
324     {
325         ExistingEndpoints ee;
326         ee.emplace_back(owner, std::move(assoc));
327         assocMaps.pending.emplace(objectPath, std::move(ee));
328     }
329     else
330     {
331         // Already waiting on this path for another association,
332         // so just add this endpoint and owner.
333         auto& endpoints = p->second;
334         auto e =
335             std::find_if(endpoints.begin(), endpoints.end(),
336                          [&assoc, &owner](const auto& endpoint) {
337                              return (std::get<ownerPos>(endpoint) == owner) &&
338                                     (std::get<assocPos>(endpoint) == assoc);
339                          });
340         if (e == endpoints.end())
341         {
342             endpoints.emplace_back(owner, std::move(assoc));
343         }
344     }
345 }
346 
removeFromPendingAssociations(const std::string & endpointPath,AssociationMaps & assocMaps)347 void removeFromPendingAssociations(const std::string& endpointPath,
348                                    AssociationMaps& assocMaps)
349 {
350     auto assoc = assocMaps.pending.begin();
351     while (assoc != assocMaps.pending.end())
352     {
353         auto endpoint = assoc->second.begin();
354         while (endpoint != assoc->second.end())
355         {
356             auto& e = std::get<assocPos>(*endpoint);
357             if (std::get<reversePathPos>(e) == endpointPath)
358             {
359                 endpoint = assoc->second.erase(endpoint);
360                 continue;
361             }
362 
363             endpoint++;
364         }
365 
366         if (assoc->second.empty())
367         {
368             assoc = assocMaps.pending.erase(assoc);
369             continue;
370         }
371 
372         assoc++;
373     }
374 }
375 
addSingleAssociation(boost::asio::io_context & io,sdbusplus::asio::object_server & server,const std::string & assocPath,const std::string & endpoint,const std::string & owner,const std::string & ownerPath,AssociationMaps & assocMaps)376 void addSingleAssociation(
377     boost::asio::io_context& io, sdbusplus::asio::object_server& server,
378     const std::string& assocPath, const std::string& endpoint,
379     const std::string& owner, const std::string& ownerPath,
380     AssociationMaps& assocMaps)
381 {
382     boost::container::flat_set<std::string> endpoints{endpoint};
383 
384     addEndpointsToAssocIfaces(io, server, assocPath, endpoints, assocMaps);
385 
386     AssociationPaths objects;
387     boost::container::flat_set e{endpoint};
388     objects.emplace(assocPath, e);
389 
390     auto a = assocMaps.owners.find(ownerPath);
391     if (a != assocMaps.owners.end())
392     {
393         auto o = a->second.find(owner);
394         if (o != a->second.end())
395         {
396             auto p = o->second.find(assocPath);
397             if (p != o->second.end())
398             {
399                 p->second.emplace(endpoint);
400             }
401             else
402             {
403                 o->second.emplace(assocPath, e);
404             }
405         }
406         else
407         {
408             a->second.emplace(owner, std::move(objects));
409         }
410     }
411     else
412     {
413         boost::container::flat_map<std::string, AssociationPaths> owners;
414         owners.emplace(owner, std::move(objects));
415         assocMaps.owners.emplace(endpoint, owners);
416     }
417 }
418 
checkIfPendingAssociation(boost::asio::io_context & io,const std::string & objectPath,const InterfaceMapType & interfaceMap,AssociationMaps & assocMaps,sdbusplus::asio::object_server & server)419 void checkIfPendingAssociation(
420     boost::asio::io_context& io, const std::string& objectPath,
421     const InterfaceMapType& interfaceMap, AssociationMaps& assocMaps,
422     sdbusplus::asio::object_server& server)
423 {
424     auto pending = assocMaps.pending.find(objectPath);
425     if (pending == assocMaps.pending.end())
426     {
427         return;
428     }
429 
430     if (!interfaceMap.contains(objectPath))
431     {
432         return;
433     }
434 
435     auto endpoint = pending->second.begin();
436 
437     while (endpoint != pending->second.end())
438     {
439         const auto& e = std::get<assocPos>(*endpoint);
440 
441         // Ensure the other side of the association still exists
442         if (!interfaceMap.contains(std::get<reversePathPos>(e)))
443         {
444             endpoint++;
445             continue;
446         }
447 
448         // Add both sides of the association:
449         //  objectPath/forwardType and reversePath/reverseType
450         //
451         // The ownerPath is the reversePath - i.e. the endpoint that
452         // is on D-Bus and owns the org.openbmc.Associations iface.
453         //
454         const auto& ownerPath = std::get<reversePathPos>(e);
455         const auto& owner = std::get<ownerPos>(*endpoint);
456 
457         auto assocPath = objectPath + '/' + std::get<forwardTypePos>(e);
458         auto endpointPath = ownerPath;
459 
460         try
461         {
462             addSingleAssociation(io, server, assocPath, endpointPath, owner,
463                                  ownerPath, assocMaps);
464 
465             // Now the reverse direction (still the same owner and ownerPath)
466             assocPath = endpointPath + '/' + std::get<reverseTypePos>(e);
467             endpointPath = objectPath;
468             addSingleAssociation(io, server, assocPath, endpointPath, owner,
469                                  ownerPath, assocMaps);
470         }
471         catch (const sdbusplus::exception_t& ex)
472         {
473             // In some case the interface could not be created on DBus and an
474             // exception is thrown. mapper has no control of the interface/path
475             // of the associations, so it has to catch the error and drop the
476             // association request.
477             std::cerr << "Error adding association: assocPath " << assocPath
478                       << ", endpointPath " << endpointPath
479                       << ", what: " << ex.what() << "\n";
480         }
481 
482         // Not pending anymore
483         endpoint = pending->second.erase(endpoint);
484     }
485 
486     if (pending->second.empty())
487     {
488         assocMaps.pending.erase(objectPath);
489     }
490 }
491 
findAssociations(const std::string & endpointPath,AssociationMaps & assocMaps,FindAssocResults & associationData)492 void findAssociations(const std::string& endpointPath,
493                       AssociationMaps& assocMaps,
494                       FindAssocResults& associationData)
495 {
496     for (const auto& [sourcePath, owners] : assocMaps.owners)
497     {
498         for (const auto& [owner, assocs] : owners)
499         {
500             for (const auto& [assocPath, endpoints] : assocs)
501             {
502                 if (std::find(endpoints.begin(), endpoints.end(),
503                               endpointPath) != endpoints.end())
504                 {
505                     // assocPath is <path>/<type> which tells us what is on the
506                     // other side of the association.
507                     auto pos = assocPath.rfind('/');
508                     auto otherPath = assocPath.substr(0, pos);
509                     auto otherType = assocPath.substr(pos + 1);
510 
511                     // Now we need to find the endpointPath/<type> ->
512                     // [otherPath] entry so that we can get the type for
513                     // endpointPath's side of the assoc.  Do this by finding
514                     // otherPath as an endpoint, and also checking for
515                     // 'endpointPath/*' as the key.
516                     auto a = std::find_if(
517                         assocs.begin(), assocs.end(),
518                         [&endpointPath, &otherPath](const auto& ap) {
519                             const auto& otherEndpoints = ap.second;
520                             auto endpoint =
521                                 std::find(otherEndpoints.begin(),
522                                           otherEndpoints.end(), otherPath);
523                             if (endpoint != otherEndpoints.end())
524                             {
525                                 return ap.first.starts_with(endpointPath + '/');
526                             }
527                             return false;
528                         });
529 
530                     if (a != assocs.end())
531                     {
532                         // Pull out the type from endpointPath/<type>
533                         pos = a->first.rfind('/');
534                         auto thisType = a->first.substr(pos + 1);
535 
536                         // Now we know the full association:
537                         // endpointPath/thisType -> otherPath/otherType
538                         Association association{thisType, otherType, otherPath};
539                         associationData.emplace_back(owner, association);
540                     }
541                 }
542             }
543         }
544     }
545 }
546 
547 /** @brief Remove an endpoint for a particular association from D-Bus.
548  *
549  * If the last endpoint is gone, remove the whole association interface,
550  * otherwise just update the D-Bus endpoints property.
551  *
552  * @param[in] assocPath     - the association path
553  * @param[in] endpointPath  - the endpoint path to find and remove
554  * @param[in,out] assocMaps - the association maps
555  * @param[in,out] server    - sdbus system object
556  */
removeAssociationIfacesEntry(boost::asio::io_context & io,const std::string & assocPath,const std::string & endpointPath,AssociationMaps & assocMaps,sdbusplus::asio::object_server & server)557 static void removeAssociationIfacesEntry(
558     boost::asio::io_context& io, const std::string& assocPath,
559     const std::string& endpointPath, AssociationMaps& assocMaps,
560     sdbusplus::asio::object_server& server)
561 {
562     auto assoc = assocMaps.ifaces.find(assocPath);
563     if (assoc != assocMaps.ifaces.end())
564     {
565         auto& endpoints = std::get<endpointsPos>(assoc->second);
566         auto e = std::find(endpoints.begin(), endpoints.end(), endpointPath);
567         if (e != endpoints.end())
568         {
569             endpoints.erase(e);
570 
571             scheduleUpdateEndpointsOnDbus(io, server, assocPath, assocMaps);
572         }
573     }
574 }
575 
576 /** @brief Remove an endpoint from the association owners map.
577  *
578  * For a specific association path and owner, remove the endpoint.
579  * Remove all remaining artifacts of that endpoint in the owners map
580  * based on what frees up after the erase.
581  *
582  * @param[in] assocPath     - the association object path
583  * @param[in] endpointPath  - the endpoint object path
584  * @param[in] owner         - the owner of the association
585  * @param[in,out] assocMaps - the association maps
586  */
removeAssociationOwnersEntry(const std::string & assocPath,const std::string & endpointPath,const std::string & owner,AssociationMaps & assocMaps)587 static void removeAssociationOwnersEntry(
588     const std::string& assocPath, const std::string& endpointPath,
589     const std::string& owner, AssociationMaps& assocMaps)
590 {
591     auto sources = assocMaps.owners.begin();
592     while (sources != assocMaps.owners.end())
593     {
594         auto owners = sources->second.find(owner);
595         if (owners != sources->second.end())
596         {
597             auto entry = owners->second.find(assocPath);
598             if (entry != owners->second.end())
599             {
600                 auto e = std::find(entry->second.begin(), entry->second.end(),
601                                    endpointPath);
602                 if (e != entry->second.end())
603                 {
604                     entry->second.erase(e);
605                     if (entry->second.empty())
606                     {
607                         owners->second.erase(entry);
608                     }
609                 }
610             }
611 
612             if (owners->second.empty())
613             {
614                 sources->second.erase(owners);
615             }
616         }
617 
618         if (sources->second.empty())
619         {
620             sources = assocMaps.owners.erase(sources);
621             continue;
622         }
623         sources++;
624     }
625 }
626 
moveAssociationToPending(boost::asio::io_context & io,const std::string & endpointPath,AssociationMaps & assocMaps,sdbusplus::asio::object_server & server)627 void moveAssociationToPending(
628     boost::asio::io_context& io, const std::string& endpointPath,
629     AssociationMaps& assocMaps, sdbusplus::asio::object_server& server)
630 {
631     FindAssocResults associationData;
632 
633     // Check which associations this path is an endpoint of, and
634     // then add them to the pending associations map and remove
635     // the associations objects.
636     findAssociations(endpointPath, assocMaps, associationData);
637 
638     for (const auto& [owner, association] : associationData)
639     {
640         const auto& forwardPath = endpointPath;
641         const auto& forwardType = std::get<forwardTypePos>(association);
642         const auto& reversePath = std::get<reversePathPos>(association);
643         const auto& reverseType = std::get<reverseTypePos>(association);
644 
645         addPendingAssociation(forwardPath, forwardType, reversePath,
646                               reverseType, owner, assocMaps);
647 
648         // Remove both sides of the association from assocMaps.ifaces
649         removeAssociationIfacesEntry(
650             io, appendPathSegment(forwardPath, forwardType), reversePath,
651             assocMaps, server);
652         removeAssociationIfacesEntry(
653             io, appendPathSegment(reversePath, reverseType), forwardPath,
654             assocMaps, server);
655 
656         // Remove both sides of the association from assocMaps.owners
657         removeAssociationOwnersEntry(
658             appendPathSegment(forwardPath, forwardType), reversePath, owner,
659             assocMaps);
660         removeAssociationOwnersEntry(
661             appendPathSegment(reversePath, reverseType), forwardPath, owner,
662             assocMaps);
663     }
664 }
665