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