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