| #include "associations.hpp" | |
| #include "path.hpp" | |
| #include <boost/asio/steady_timer.hpp> | |
| #include <sdbusplus/exception.hpp> | |
| #include <iostream> | |
| #include <string> | |
| static void updateEndpointsOnDbus(sdbusplus::asio::object_server& objectServer, | |
| const std::string& assocPath, | |
| AssociationMaps& assocMaps) | |
| { | |
| // Don't create an entry in assocMaps.ifaces if not needed. | |
| auto iface = assocMaps.ifaces.find(assocPath); | |
| if (iface == assocMaps.ifaces.end()) | |
| { | |
| return; | |
| } | |
| auto& i = std::get<ifacePos>(iface->second); | |
| auto& endpoints = std::get<endpointsPos>(iface->second); | |
| // If the interface already exists, only need to update | |
| // the property value, otherwise create it | |
| if (i) | |
| { | |
| if (endpoints.empty()) | |
| { | |
| objectServer.remove_interface(i); | |
| i = nullptr; | |
| } | |
| else | |
| { | |
| i->set_property("endpoints", endpoints); | |
| } | |
| } | |
| else if (!endpoints.empty()) | |
| { | |
| i = objectServer.add_interface(assocPath, xyzAssociationInterface); | |
| i->register_property("endpoints", endpoints); | |
| i->initialize(); | |
| } | |
| if (endpoints.empty()) | |
| { | |
| assocMaps.ifaces.erase(iface); | |
| } | |
| } | |
| static void scheduleUpdateEndpointsOnDbus( | |
| boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, | |
| const std::string& assocPath, AssociationMaps& assocMaps) | |
| { | |
| static std::set<std::string> delayedUpdatePaths; | |
| if (delayedUpdatePaths.contains(assocPath)) | |
| { | |
| return; | |
| } | |
| auto iface = assocMaps.ifaces.find(assocPath); | |
| if (iface == assocMaps.ifaces.end()) | |
| { | |
| return; | |
| } | |
| auto& endpoints = std::get<endpointsPos>(iface->second); | |
| if (endpoints.size() > endpointsCountTimerThreshold) | |
| { | |
| delayedUpdatePaths.emplace(assocPath); | |
| auto timer = std::make_shared<boost::asio::steady_timer>( | |
| io, std::chrono::seconds(endpointUpdateDelaySeconds)); | |
| timer->async_wait([&objectServer, &assocMaps, timer, | |
| assocPath](const boost::system::error_code& ec) { | |
| if (!ec) | |
| { | |
| updateEndpointsOnDbus(objectServer, assocPath, assocMaps); | |
| } | |
| delayedUpdatePaths.erase(assocPath); | |
| }); | |
| } | |
| else | |
| { | |
| updateEndpointsOnDbus(objectServer, assocPath, assocMaps); | |
| } | |
| } | |
| void removeAssociation(boost::asio::io_context& io, | |
| const std::string& sourcePath, const std::string& owner, | |
| sdbusplus::asio::object_server& server, | |
| AssociationMaps& assocMaps) | |
| { | |
| // Use associationOwners to find the association paths and endpoints | |
| // that the passed in object path and service own. Remove all of | |
| // these endpoints from the actual association D-Bus objects, and if | |
| // the endpoints property is then empty, the whole association object | |
| // can be removed. Note there can be multiple services that own an | |
| // association, and also that sourcePath is the path of the object | |
| // that contains the org.openbmc.Associations interface and not the | |
| // association path itself. | |
| // Find the services that have associations for this object path | |
| auto owners = assocMaps.owners.find(sourcePath); | |
| if (owners == assocMaps.owners.end()) | |
| { | |
| return; | |
| } | |
| // Find the association paths and endpoints owned by this object | |
| // path for this service. | |
| auto assocs = owners->second.find(owner); | |
| if (assocs == owners->second.end()) | |
| { | |
| return; | |
| } | |
| for (const auto& [assocPath, endpointsToRemove] : assocs->second) | |
| { | |
| removeAssociationEndpoints(io, server, assocPath, endpointsToRemove, | |
| assocMaps); | |
| } | |
| // Remove the associationOwners entries for this owning path/service. | |
| owners->second.erase(assocs); | |
| if (owners->second.empty()) | |
| { | |
| assocMaps.owners.erase(owners); | |
| } | |
| // If we were still waiting on the other side of this association to | |
| // show up, cancel that wait. | |
| removeFromPendingAssociations(sourcePath, owner, assocMaps); | |
| } | |
| void 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) | |
| { | |
| auto assoc = assocMaps.ifaces.find(assocPath); | |
| if (assoc == assocMaps.ifaces.end()) | |
| { | |
| return; | |
| } | |
| auto& endpointsInDBus = std::get<endpointsPos>(assoc->second); | |
| for (const auto& endpointToRemove : endpointsToRemove) | |
| { | |
| auto e = std::find(endpointsInDBus.begin(), endpointsInDBus.end(), | |
| endpointToRemove); | |
| if (e != endpointsInDBus.end()) | |
| { | |
| endpointsInDBus.erase(e); | |
| } | |
| } | |
| scheduleUpdateEndpointsOnDbus(io, objectServer, assocPath, assocMaps); | |
| } | |
| void checkAssociationEndpointRemoves( | |
| boost::asio::io_context& io, const std::string& sourcePath, | |
| const std::string& owner, const AssociationPaths& newAssociations, | |
| sdbusplus::asio::object_server& objectServer, AssociationMaps& assocMaps) | |
| { | |
| // Find the services that have associations on this path. | |
| auto originalOwners = assocMaps.owners.find(sourcePath); | |
| if (originalOwners == assocMaps.owners.end()) | |
| { | |
| return; | |
| } | |
| // Find the associations for this service | |
| auto originalAssociations = originalOwners->second.find(owner); | |
| if (originalAssociations == originalOwners->second.end()) | |
| { | |
| return; | |
| } | |
| // Compare the new endpoints versus the original endpoints, and | |
| // remove any of the original ones that aren't in the new list. | |
| for (const auto& [originalAssocPath, originalEndpoints] : | |
| originalAssociations->second) | |
| { | |
| // Check if this source even still has each association that | |
| // was there previously, and if not, remove all of its endpoints | |
| // from the D-Bus endpoints property which will cause the whole | |
| // association path to be removed if no endpoints remain. | |
| auto newEndpoints = newAssociations.find(originalAssocPath); | |
| if (newEndpoints == newAssociations.end()) | |
| { | |
| removeAssociationEndpoints(io, objectServer, originalAssocPath, | |
| originalEndpoints, assocMaps); | |
| } | |
| else | |
| { | |
| // The association is still there. Check if the endpoints | |
| // changed. | |
| boost::container::flat_set<std::string> toRemove; | |
| for (const auto& originalEndpoint : originalEndpoints) | |
| { | |
| if (std::find(newEndpoints->second.begin(), | |
| newEndpoints->second.end(), originalEndpoint) == | |
| newEndpoints->second.end()) | |
| { | |
| toRemove.emplace(originalEndpoint); | |
| } | |
| } | |
| if (!toRemove.empty()) | |
| { | |
| removeAssociationEndpoints(io, objectServer, originalAssocPath, | |
| toRemove, assocMaps); | |
| } | |
| } | |
| } | |
| } | |
| static void 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) | |
| { | |
| auto& iface = assocMaps.ifaces[assocPath]; | |
| auto& endpoints = std::get<endpointsPos>(iface); | |
| // Only add new endpoints | |
| for (const auto& e : endpointPaths) | |
| { | |
| if (std::find(endpoints.begin(), endpoints.end(), e) == endpoints.end()) | |
| { | |
| endpoints.push_back(e); | |
| } | |
| } | |
| scheduleUpdateEndpointsOnDbus(io, objectServer, assocPath, assocMaps); | |
| } | |
| void associationChanged( | |
| boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, | |
| const std::vector<Association>& associations, | |
| const sdbusplus::object_path& path, const std::string& owner, | |
| const InterfaceMapType& interfaceMap, AssociationMaps& assocMaps) | |
| { | |
| AssociationPaths objects; | |
| for (const Association& association : associations) | |
| { | |
| std::string forward; | |
| std::string reverse; | |
| std::string objectPath; | |
| std::tie(forward, reverse, objectPath) = association; | |
| if (objectPath.empty()) | |
| { | |
| std::cerr << "Found invalid association on path " << path.string() | |
| << "\n"; | |
| continue; | |
| } | |
| // Can't create this association if the endpoint isn't on D-Bus. | |
| if (!interfaceMap.contains(objectPath)) | |
| { | |
| addPendingAssociation(objectPath, reverse, path, forward, owner, | |
| assocMaps); | |
| continue; | |
| } | |
| if (!forward.empty()) | |
| { | |
| objects[appendPathSegment(path, forward)].emplace(objectPath); | |
| } | |
| if (!reverse.empty()) | |
| { | |
| objects[appendPathSegment(objectPath, reverse)].emplace(path); | |
| } | |
| } | |
| for (const auto& object : objects) | |
| { | |
| addEndpointsToAssocIfaces(io, objectServer, object.first, object.second, | |
| assocMaps); | |
| } | |
| // Check for endpoints being removed instead of added | |
| checkAssociationEndpointRemoves(io, path, owner, objects, objectServer, | |
| assocMaps); | |
| if (!objects.empty()) | |
| { | |
| // Update associationOwners with the latest info | |
| auto a = assocMaps.owners.find(path); | |
| if (a != assocMaps.owners.end()) | |
| { | |
| auto o = a->second.find(owner); | |
| if (o != a->second.end()) | |
| { | |
| o->second = std::move(objects); | |
| } | |
| else | |
| { | |
| a->second.emplace(owner, std::move(objects)); | |
| } | |
| } | |
| else | |
| { | |
| boost::container::flat_map<std::string, AssociationPaths> owners; | |
| owners.emplace(owner, std::move(objects)); | |
| assocMaps.owners.emplace(path, owners); | |
| } | |
| } | |
| } | |
| void addPendingAssociation( | |
| const std::string& objectPath, const std::string& type, | |
| const std::string& endpointPath, const std::string& endpointType, | |
| const std::string& owner, AssociationMaps& assocMaps) | |
| { | |
| Association assoc{type, endpointType, endpointPath}; | |
| auto p = assocMaps.pending.find(objectPath); | |
| if (p == assocMaps.pending.end()) | |
| { | |
| ExistingEndpoints ee; | |
| ee.emplace_back(owner, std::move(assoc)); | |
| assocMaps.pending.emplace(objectPath, std::move(ee)); | |
| } | |
| else | |
| { | |
| // Already waiting on this path for another association, | |
| // so just add this endpoint and owner. | |
| auto& endpoints = p->second; | |
| auto e = | |
| std::find_if(endpoints.begin(), endpoints.end(), | |
| [&assoc, &owner](const auto& endpoint) { | |
| return (std::get<ownerPos>(endpoint) == owner) && | |
| (std::get<assocPos>(endpoint) == assoc); | |
| }); | |
| if (e == endpoints.end()) | |
| { | |
| endpoints.emplace_back(owner, std::move(assoc)); | |
| } | |
| } | |
| } | |
| void removeFromPendingAssociations(const std::string& endpointPath, | |
| const std::string& owner, | |
| AssociationMaps& assocMaps) | |
| { | |
| auto assoc = assocMaps.pending.begin(); | |
| while (assoc != assocMaps.pending.end()) | |
| { | |
| auto endpoint = assoc->second.begin(); | |
| while (endpoint != assoc->second.end()) | |
| { | |
| auto& e = std::get<assocPos>(*endpoint); | |
| if (std::get<reversePathPos>(e) == endpointPath && | |
| std::get<ownerPos>(*endpoint) == owner) | |
| { | |
| endpoint = assoc->second.erase(endpoint); | |
| continue; | |
| } | |
| endpoint++; | |
| } | |
| if (assoc->second.empty()) | |
| { | |
| assoc = assocMaps.pending.erase(assoc); | |
| continue; | |
| } | |
| assoc++; | |
| } | |
| } | |
| void 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) | |
| { | |
| boost::container::flat_set<std::string> endpoints{endpoint}; | |
| addEndpointsToAssocIfaces(io, server, assocPath, endpoints, assocMaps); | |
| AssociationPaths objects; | |
| boost::container::flat_set e{endpoint}; | |
| objects.emplace(assocPath, e); | |
| auto a = assocMaps.owners.find(ownerPath); | |
| if (a != assocMaps.owners.end()) | |
| { | |
| auto o = a->second.find(owner); | |
| if (o != a->second.end()) | |
| { | |
| auto p = o->second.find(assocPath); | |
| if (p != o->second.end()) | |
| { | |
| p->second.emplace(endpoint); | |
| } | |
| else | |
| { | |
| o->second.emplace(assocPath, e); | |
| } | |
| } | |
| else | |
| { | |
| a->second.emplace(owner, std::move(objects)); | |
| } | |
| } | |
| else | |
| { | |
| boost::container::flat_map<std::string, AssociationPaths> owners; | |
| owners.emplace(owner, std::move(objects)); | |
| assocMaps.owners.emplace(endpoint, owners); | |
| } | |
| } | |
| void checkIfPendingAssociation( | |
| boost::asio::io_context& io, const sdbusplus::object_path& objectPath, | |
| const InterfaceMapType& interfaceMap, AssociationMaps& assocMaps, | |
| sdbusplus::asio::object_server& server) | |
| { | |
| auto pending = assocMaps.pending.find(objectPath); | |
| if (pending == assocMaps.pending.end()) | |
| { | |
| return; | |
| } | |
| if (!interfaceMap.contains(objectPath)) | |
| { | |
| return; | |
| } | |
| auto endpoint = pending->second.begin(); | |
| while (endpoint != pending->second.end()) | |
| { | |
| const auto& e = std::get<assocPos>(*endpoint); | |
| // Ensure the other side of the association still exists | |
| if (!interfaceMap.contains(std::get<reversePathPos>(e))) | |
| { | |
| endpoint++; | |
| continue; | |
| } | |
| // Add both sides of the association: | |
| // objectPath/forwardType and reversePath/reverseType | |
| // | |
| // The ownerPath is the reversePath - i.e. the endpoint that | |
| // is on D-Bus and owns the org.openbmc.Associations iface. | |
| // | |
| const auto& ownerPath = std::get<reversePathPos>(e); | |
| const auto& owner = std::get<ownerPos>(*endpoint); | |
| auto assocPath = objectPath / std::get<forwardTypePos>(e); | |
| auto endpointPath = ownerPath; | |
| try | |
| { | |
| addSingleAssociation(io, server, assocPath, endpointPath, owner, | |
| ownerPath, assocMaps); | |
| // Now the reverse direction (still the same owner and ownerPath) | |
| assocPath = endpointPath + '/' + std::get<reverseTypePos>(e); | |
| endpointPath = objectPath; | |
| addSingleAssociation(io, server, assocPath, endpointPath, owner, | |
| ownerPath, assocMaps); | |
| } | |
| catch (const sdbusplus::exception_t& ex) | |
| { | |
| // In some case the interface could not be created on DBus and an | |
| // exception is thrown. mapper has no control of the interface/path | |
| // of the associations, so it has to catch the error and drop the | |
| // association request. | |
| std::cerr << "Error adding association: assocPath " | |
| << assocPath.string() << ", endpointPath " << endpointPath | |
| << ", what: " << ex.what() << "\n"; | |
| } | |
| // Not pending anymore | |
| endpoint = pending->second.erase(endpoint); | |
| } | |
| if (pending->second.empty()) | |
| { | |
| assocMaps.pending.erase(objectPath); | |
| } | |
| } | |
| void findAssociations(const std::string& endpointPath, | |
| AssociationMaps& assocMaps, | |
| FindAssocResults& associationData) | |
| { | |
| for (const auto& [sourcePath, owners] : assocMaps.owners) | |
| { | |
| for (const auto& [owner, assocs] : owners) | |
| { | |
| for (const auto& [assocPath, endpoints] : assocs) | |
| { | |
| if (std::find(endpoints.begin(), endpoints.end(), | |
| endpointPath) != endpoints.end()) | |
| { | |
| // assocPath is <path>/<type> which tells us what is on the | |
| // other side of the association. | |
| auto pos = assocPath.rfind('/'); | |
| auto otherPath = assocPath.substr(0, pos); | |
| auto otherType = assocPath.substr(pos + 1); | |
| // Now we need to find the endpointPath/<type> -> | |
| // [otherPath] entry so that we can get the type for | |
| // endpointPath's side of the assoc. Do this by finding | |
| // otherPath as an endpoint, and also checking for | |
| // 'endpointPath/*' as the key. | |
| auto a = std::find_if( | |
| assocs.begin(), assocs.end(), | |
| [&endpointPath, &otherPath](const auto& ap) { | |
| const auto& otherEndpoints = ap.second; | |
| auto endpoint = | |
| std::find(otherEndpoints.begin(), | |
| otherEndpoints.end(), otherPath); | |
| if (endpoint != otherEndpoints.end()) | |
| { | |
| return ap.first.starts_with(endpointPath + '/'); | |
| } | |
| return false; | |
| }); | |
| if (a != assocs.end()) | |
| { | |
| // Pull out the type from endpointPath/<type> | |
| pos = a->first.rfind('/'); | |
| auto thisType = a->first.substr(pos + 1); | |
| // Now we know the full association: | |
| // endpointPath/thisType -> otherPath/otherType | |
| Association association{thisType, otherType, otherPath}; | |
| associationData.emplace_back(owner, association); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /** @brief Remove an endpoint for a particular association from D-Bus. | |
| * | |
| * If the last endpoint is gone, remove the whole association interface, | |
| * otherwise just update the D-Bus endpoints property. | |
| * | |
| * @param[in] assocPath - the association path | |
| * @param[in] endpointPath - the endpoint path to find and remove | |
| * @param[in,out] assocMaps - the association maps | |
| * @param[in,out] server - sdbus system object | |
| */ | |
| static void removeAssociationIfacesEntry( | |
| boost::asio::io_context& io, const std::string& assocPath, | |
| const std::string& endpointPath, AssociationMaps& assocMaps, | |
| sdbusplus::asio::object_server& server) | |
| { | |
| auto assoc = assocMaps.ifaces.find(assocPath); | |
| if (assoc != assocMaps.ifaces.end()) | |
| { | |
| auto& endpoints = std::get<endpointsPos>(assoc->second); | |
| auto e = std::find(endpoints.begin(), endpoints.end(), endpointPath); | |
| if (e != endpoints.end()) | |
| { | |
| endpoints.erase(e); | |
| scheduleUpdateEndpointsOnDbus(io, server, assocPath, assocMaps); | |
| } | |
| } | |
| } | |
| /** @brief Remove an endpoint from the association owners map. | |
| * | |
| * For a specific association path and owner, remove the endpoint. | |
| * Remove all remaining artifacts of that endpoint in the owners map | |
| * based on what frees up after the erase. | |
| * | |
| * @param[in] assocPath - the association object path | |
| * @param[in] endpointPath - the endpoint object path | |
| * @param[in] owner - the owner of the association | |
| * @param[in,out] assocMaps - the association maps | |
| */ | |
| static void removeAssociationOwnersEntry( | |
| const std::string& assocPath, const std::string& endpointPath, | |
| const std::string& owner, AssociationMaps& assocMaps) | |
| { | |
| auto sources = assocMaps.owners.begin(); | |
| while (sources != assocMaps.owners.end()) | |
| { | |
| auto owners = sources->second.find(owner); | |
| if (owners != sources->second.end()) | |
| { | |
| auto entry = owners->second.find(assocPath); | |
| if (entry != owners->second.end()) | |
| { | |
| auto e = std::find(entry->second.begin(), entry->second.end(), | |
| endpointPath); | |
| if (e != entry->second.end()) | |
| { | |
| entry->second.erase(e); | |
| if (entry->second.empty()) | |
| { | |
| owners->second.erase(entry); | |
| } | |
| } | |
| } | |
| if (owners->second.empty()) | |
| { | |
| sources->second.erase(owners); | |
| } | |
| } | |
| if (sources->second.empty()) | |
| { | |
| sources = assocMaps.owners.erase(sources); | |
| continue; | |
| } | |
| sources++; | |
| } | |
| } | |
| void moveAssociationToPending( | |
| boost::asio::io_context& io, const sdbusplus::object_path& endpointPath, | |
| AssociationMaps& assocMaps, sdbusplus::asio::object_server& server) | |
| { | |
| FindAssocResults associationData; | |
| // Check which associations this path is an endpoint of, and | |
| // then add them to the pending associations map and remove | |
| // the associations objects. | |
| findAssociations(endpointPath, assocMaps, associationData); | |
| for (const auto& [owner, association] : associationData) | |
| { | |
| const auto& forwardPath = endpointPath; | |
| const auto& forwardType = std::get<forwardTypePos>(association); | |
| const auto& reversePath = std::get<reversePathPos>(association); | |
| const auto& reverseType = std::get<reverseTypePos>(association); | |
| addPendingAssociation(forwardPath, forwardType, reversePath, | |
| reverseType, owner, assocMaps); | |
| // Remove both sides of the association from assocMaps.ifaces | |
| removeAssociationIfacesEntry( | |
| io, appendPathSegment(forwardPath, forwardType), reversePath, | |
| assocMaps, server); | |
| removeAssociationIfacesEntry( | |
| io, appendPathSegment(reversePath, reverseType), forwardPath, | |
| assocMaps, server); | |
| // Remove both sides of the association from assocMaps.owners | |
| removeAssociationOwnersEntry( | |
| appendPathSegment(forwardPath, forwardType), reversePath, owner, | |
| assocMaps); | |
| removeAssociationOwnersEntry( | |
| appendPathSegment(reversePath, reverseType), forwardPath, owner, | |
| assocMaps); | |
| } | |
| } |