blob: 927eec123a81c3be3df3729f00d387f34ed5ecd6 [file] [log] [blame]
#include "associations.hpp"
#include <boost/asio/steady_timer.hpp>
#include <sdbusplus/exception.hpp>
#include <iostream>
#include <string>
void updateEndpointsOnDbus(sdbusplus::asio::object_server& objectServer,
const std::string& assocPath,
AssociationMaps& assocMaps)
{
auto& iface = assocMaps.ifaces[assocPath];
auto& i = std::get<ifacePos>(iface);
auto& endpoints = std::get<endpointsPos>(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<std::string> delayedUpdatePaths;
if (delayedUpdatePaths.contains(assocPath))
{
return;
}
auto& iface = assocMaps.ifaces[assocPath];
auto& endpoints = std::get<endpointsPos>(iface);
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, 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);
}
}
}
}
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 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<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,
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)
{
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 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<assocPos>(*endpoint);
// Ensure the other side of the association still exists
if (interfaceMap.find(std::get<reversePathPos>(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<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& 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 <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& 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/<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
*/
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
*/
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<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, 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);
}
}