#include "associations.hpp" #include #include #include #include void updateEndpointsOnDbus(sdbusplus::asio::object_server& objectServer, const std::string& assocPath, AssociationMaps& assocMaps) { auto& iface = assocMaps.ifaces[assocPath]; auto& i = std::get(iface); auto& endpoints = std::get(iface); // 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(); } } } void scheduleUpdateEndpointsOnDbus( boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, const std::string& assocPath, AssociationMaps& assocMaps) { static std::set delayedUpdatePaths; if (delayedUpdatePaths.contains(assocPath)) { return; } auto& iface = assocMaps.ifaces[assocPath]; auto& endpoints = std::get(iface); if (endpoints.size() > endpointsCountTimerThreshold) { delayedUpdatePaths.emplace(assocPath); auto timer = std::make_shared( 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, assocMaps); } void removeAssociationEndpoints( boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, const std::string& assocPath, const boost::container::flat_set& endpointsToRemove, AssociationMaps& assocMaps) { auto assoc = assocMaps.ifaces.find(assocPath); if (assoc == assocMaps.ifaces.end()) { return; } auto& endpointsInDBus = std::get(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 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); } } } } void addEndpointsToAssocIfaces( boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, const std::string& assocPath, const boost::container::flat_set& endpointPaths, AssociationMaps& assocMaps) { auto& iface = assocMaps.ifaces[assocPath]; auto& endpoints = std::get(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& associations, const std::string& 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 << "\n"; continue; } // Can't create this association if the endpoint isn't on D-Bus. if (interfaceMap.find(objectPath) == interfaceMap.end()) { addPendingAssociation(objectPath, reverse, path, forward, owner, assocMaps); continue; } if (!forward.empty()) { objects[path + "/" + forward].emplace(objectPath); } if (!reverse.empty()) { objects[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 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(endpoint) == owner) && (std::get(endpoint) == assoc); }); if (e == endpoints.end()) { endpoints.emplace_back(owner, std::move(assoc)); } } } void removeFromPendingAssociations(const std::string& endpointPath, 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(*endpoint); if (std::get(e) == endpointPath) { 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 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 owners; owners.emplace(owner, std::move(objects)); assocMaps.owners.emplace(endpoint, owners); } } void checkIfPendingAssociation( boost::asio::io_context& io, const std::string& 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.find(objectPath) == interfaceMap.end()) { return; } auto endpoint = pending->second.begin(); while (endpoint != pending->second.end()) { const auto& e = std::get(*endpoint); // Ensure the other side of the association still exists if (interfaceMap.find(std::get(e)) == interfaceMap.end()) { 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(e); const auto& owner = std::get(*endpoint); auto assocPath = objectPath + '/' + std::get(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(e); endpointPath = objectPath; addSingleAssociation(io, server, assocPath, endpointPath, owner, ownerPath, assocMaps); } catch (const sdbusplus::exception_t& e) { // 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 << ", endpointPath " << endpointPath << ", what: " << e.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 / 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/ -> // [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& endpoints = ap.second; auto endpoint = std::find( endpoints.begin(), endpoints.end(), otherPath); if (endpoint != endpoints.end()) { return ap.first.starts_with(endpointPath + '/'); } return false; }); if (a != assocs.end()) { // Pull out the type from endpointPath/ 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 */ 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(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 */ 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 std::string& 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(association); const auto& reversePath = std::get(association); const auto& reverseType = std::get(association); addPendingAssociation(forwardPath, forwardType, reversePath, reverseType, owner, assocMaps); // Remove both sides of the association from assocMaps.ifaces removeAssociationIfacesEntry(io, forwardPath + '/' + forwardType, reversePath, assocMaps, server); removeAssociationIfacesEntry(io, reversePath + '/' + reverseType, forwardPath, assocMaps, server); // Remove both sides of the association from assocMaps.owners removeAssociationOwnersEntry(forwardPath + '/' + forwardType, reversePath, owner, assocMaps); removeAssociationOwnersEntry(reversePath + '/' + reverseType, forwardPath, owner, assocMaps); } }