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