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 auto& iface = assocMaps.ifaces[assocPath];
14 auto& i = std::get<ifacePos>(iface);
15 auto& endpoints = std::get<endpointsPos>(iface);
16
17 // If the interface already exists, only need to update
18 // the property value, otherwise create it
19 if (i)
20 {
21 if (endpoints.empty())
22 {
23 objectServer.remove_interface(i);
24 i = nullptr;
25 }
26 else
27 {
28 i->set_property("endpoints", endpoints);
29 }
30 }
31 else
32 {
33 if (!endpoints.empty())
34 {
35 i = objectServer.add_interface(assocPath, xyzAssociationInterface);
36 i->register_property("endpoints", endpoints);
37 i->initialize();
38 }
39 }
40 }
41
scheduleUpdateEndpointsOnDbus(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,const std::string & assocPath,AssociationMaps & assocMaps)42 void scheduleUpdateEndpointsOnDbus(
43 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
44 const std::string& assocPath, AssociationMaps& assocMaps)
45 {
46 static std::set<std::string> delayedUpdatePaths;
47
48 if (delayedUpdatePaths.contains(assocPath))
49 {
50 return;
51 }
52
53 auto& iface = assocMaps.ifaces[assocPath];
54 auto& endpoints = std::get<endpointsPos>(iface);
55
56 if (endpoints.size() > endpointsCountTimerThreshold)
57 {
58 delayedUpdatePaths.emplace(assocPath);
59 auto timer = std::make_shared<boost::asio::steady_timer>(
60 io, std::chrono::seconds(endpointUpdateDelaySeconds));
61 timer->async_wait([&objectServer, &assocMaps, timer,
62 assocPath](const boost::system::error_code& ec) {
63 if (!ec)
64 {
65 updateEndpointsOnDbus(objectServer, assocPath, assocMaps);
66 }
67 delayedUpdatePaths.erase(assocPath);
68 });
69 }
70 else
71 {
72 updateEndpointsOnDbus(objectServer, assocPath, assocMaps);
73 }
74 }
75
removeAssociation(boost::asio::io_context & io,const std::string & sourcePath,const std::string & owner,sdbusplus::asio::object_server & server,AssociationMaps & assocMaps)76 void removeAssociation(boost::asio::io_context& io,
77 const std::string& sourcePath, const std::string& owner,
78 sdbusplus::asio::object_server& server,
79 AssociationMaps& assocMaps)
80 {
81 // Use associationOwners to find the association paths and endpoints
82 // that the passed in object path and service own. Remove all of
83 // these endpoints from the actual association D-Bus objects, and if
84 // the endpoints property is then empty, the whole association object
85 // can be removed. Note there can be multiple services that own an
86 // association, and also that sourcePath is the path of the object
87 // that contains the org.openbmc.Associations interface and not the
88 // association path itself.
89
90 // Find the services that have associations for this object path
91 auto owners = assocMaps.owners.find(sourcePath);
92 if (owners == assocMaps.owners.end())
93 {
94 return;
95 }
96
97 // Find the association paths and endpoints owned by this object
98 // path for this service.
99 auto assocs = owners->second.find(owner);
100 if (assocs == owners->second.end())
101 {
102 return;
103 }
104
105 for (const auto& [assocPath, endpointsToRemove] : assocs->second)
106 {
107 removeAssociationEndpoints(io, server, assocPath, endpointsToRemove,
108 assocMaps);
109 }
110
111 // Remove the associationOwners entries for this owning path/service.
112 owners->second.erase(assocs);
113 if (owners->second.empty())
114 {
115 assocMaps.owners.erase(owners);
116 }
117
118 // If we were still waiting on the other side of this association to
119 // show up, cancel that wait.
120 removeFromPendingAssociations(sourcePath, assocMaps);
121 }
122
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)123 void removeAssociationEndpoints(
124 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
125 const std::string& assocPath,
126 const boost::container::flat_set<std::string>& endpointsToRemove,
127 AssociationMaps& assocMaps)
128 {
129 auto assoc = assocMaps.ifaces.find(assocPath);
130 if (assoc == assocMaps.ifaces.end())
131 {
132 return;
133 }
134
135 auto& endpointsInDBus = std::get<endpointsPos>(assoc->second);
136
137 for (const auto& endpointToRemove : endpointsToRemove)
138 {
139 auto e = std::find(endpointsInDBus.begin(), endpointsInDBus.end(),
140 endpointToRemove);
141
142 if (e != endpointsInDBus.end())
143 {
144 endpointsInDBus.erase(e);
145 }
146 }
147
148 scheduleUpdateEndpointsOnDbus(io, objectServer, assocPath, assocMaps);
149 }
150
checkAssociationEndpointRemoves(boost::asio::io_context & io,const std::string & sourcePath,const std::string & owner,const AssociationPaths & newAssociations,sdbusplus::asio::object_server & objectServer,AssociationMaps & assocMaps)151 void checkAssociationEndpointRemoves(
152 boost::asio::io_context& io, const std::string& sourcePath,
153 const std::string& owner, const AssociationPaths& newAssociations,
154 sdbusplus::asio::object_server& objectServer, AssociationMaps& assocMaps)
155 {
156 // Find the services that have associations on this path.
157 auto originalOwners = assocMaps.owners.find(sourcePath);
158 if (originalOwners == assocMaps.owners.end())
159 {
160 return;
161 }
162
163 // Find the associations for this service
164 auto originalAssociations = originalOwners->second.find(owner);
165 if (originalAssociations == originalOwners->second.end())
166 {
167 return;
168 }
169
170 // Compare the new endpoints versus the original endpoints, and
171 // remove any of the original ones that aren't in the new list.
172 for (const auto& [originalAssocPath, originalEndpoints] :
173 originalAssociations->second)
174 {
175 // Check if this source even still has each association that
176 // was there previously, and if not, remove all of its endpoints
177 // from the D-Bus endpoints property which will cause the whole
178 // association path to be removed if no endpoints remain.
179 auto newEndpoints = newAssociations.find(originalAssocPath);
180 if (newEndpoints == newAssociations.end())
181 {
182 removeAssociationEndpoints(io, objectServer, originalAssocPath,
183 originalEndpoints, assocMaps);
184 }
185 else
186 {
187 // The association is still there. Check if the endpoints
188 // changed.
189 boost::container::flat_set<std::string> toRemove;
190
191 for (const auto& originalEndpoint : originalEndpoints)
192 {
193 if (std::find(newEndpoints->second.begin(),
194 newEndpoints->second.end(), originalEndpoint) ==
195 newEndpoints->second.end())
196 {
197 toRemove.emplace(originalEndpoint);
198 }
199 }
200 if (!toRemove.empty())
201 {
202 removeAssociationEndpoints(io, objectServer, originalAssocPath,
203 toRemove, assocMaps);
204 }
205 }
206 }
207 }
208
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)209 void addEndpointsToAssocIfaces(
210 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
211 const std::string& assocPath,
212 const boost::container::flat_set<std::string>& endpointPaths,
213 AssociationMaps& assocMaps)
214 {
215 auto& iface = assocMaps.ifaces[assocPath];
216 auto& endpoints = std::get<endpointsPos>(iface);
217
218 // Only add new endpoints
219 for (const auto& e : endpointPaths)
220 {
221 if (std::find(endpoints.begin(), endpoints.end(), e) == endpoints.end())
222 {
223 endpoints.push_back(e);
224 }
225 }
226 scheduleUpdateEndpointsOnDbus(io, objectServer, assocPath, assocMaps);
227 }
228
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)229 void associationChanged(
230 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
231 const std::vector<Association>& associations, const std::string& path,
232 const std::string& owner, const InterfaceMapType& interfaceMap,
233 AssociationMaps& assocMaps)
234 {
235 AssociationPaths objects;
236
237 for (const Association& association : associations)
238 {
239 std::string forward;
240 std::string reverse;
241 std::string objectPath;
242 std::tie(forward, reverse, objectPath) = association;
243
244 if (objectPath.empty())
245 {
246 std::cerr << "Found invalid association on path " << path << "\n";
247 continue;
248 }
249
250 // Can't create this association if the endpoint isn't on D-Bus.
251 if (interfaceMap.find(objectPath) == interfaceMap.end())
252 {
253 addPendingAssociation(objectPath, reverse, path, forward, owner,
254 assocMaps);
255 continue;
256 }
257
258 if (!forward.empty())
259 {
260 objects[path + "/" + forward].emplace(objectPath);
261 }
262 if (!reverse.empty())
263 {
264 objects[objectPath + "/" + reverse].emplace(path);
265 }
266 }
267 for (const auto& object : objects)
268 {
269 addEndpointsToAssocIfaces(io, objectServer, object.first, object.second,
270 assocMaps);
271 }
272
273 // Check for endpoints being removed instead of added
274 checkAssociationEndpointRemoves(io, path, owner, objects, objectServer,
275 assocMaps);
276
277 if (!objects.empty())
278 {
279 // Update associationOwners with the latest info
280 auto a = assocMaps.owners.find(path);
281 if (a != assocMaps.owners.end())
282 {
283 auto o = a->second.find(owner);
284 if (o != a->second.end())
285 {
286 o->second = std::move(objects);
287 }
288 else
289 {
290 a->second.emplace(owner, std::move(objects));
291 }
292 }
293 else
294 {
295 boost::container::flat_map<std::string, AssociationPaths> owners;
296 owners.emplace(owner, std::move(objects));
297 assocMaps.owners.emplace(path, owners);
298 }
299 }
300 }
301
addPendingAssociation(const std::string & objectPath,const std::string & type,const std::string & endpointPath,const std::string & endpointType,const std::string & owner,AssociationMaps & assocMaps)302 void addPendingAssociation(
303 const std::string& objectPath, const std::string& type,
304 const std::string& endpointPath, const std::string& endpointType,
305 const std::string& owner, AssociationMaps& assocMaps)
306 {
307 Association assoc{type, endpointType, endpointPath};
308
309 auto p = assocMaps.pending.find(objectPath);
310 if (p == assocMaps.pending.end())
311 {
312 ExistingEndpoints ee;
313 ee.emplace_back(owner, std::move(assoc));
314 assocMaps.pending.emplace(objectPath, std::move(ee));
315 }
316 else
317 {
318 // Already waiting on this path for another association,
319 // so just add this endpoint and owner.
320 auto& endpoints = p->second;
321 auto e =
322 std::find_if(endpoints.begin(), endpoints.end(),
323 [&assoc, &owner](const auto& endpoint) {
324 return (std::get<ownerPos>(endpoint) == owner) &&
325 (std::get<assocPos>(endpoint) == assoc);
326 });
327 if (e == endpoints.end())
328 {
329 endpoints.emplace_back(owner, std::move(assoc));
330 }
331 }
332 }
333
removeFromPendingAssociations(const std::string & endpointPath,AssociationMaps & assocMaps)334 void removeFromPendingAssociations(const std::string& endpointPath,
335 AssociationMaps& assocMaps)
336 {
337 auto assoc = assocMaps.pending.begin();
338 while (assoc != assocMaps.pending.end())
339 {
340 auto endpoint = assoc->second.begin();
341 while (endpoint != assoc->second.end())
342 {
343 auto& e = std::get<assocPos>(*endpoint);
344 if (std::get<reversePathPos>(e) == endpointPath)
345 {
346 endpoint = assoc->second.erase(endpoint);
347 continue;
348 }
349
350 endpoint++;
351 }
352
353 if (assoc->second.empty())
354 {
355 assoc = assocMaps.pending.erase(assoc);
356 continue;
357 }
358
359 assoc++;
360 }
361 }
362
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)363 void addSingleAssociation(
364 boost::asio::io_context& io, sdbusplus::asio::object_server& server,
365 const std::string& assocPath, const std::string& endpoint,
366 const std::string& owner, const std::string& ownerPath,
367 AssociationMaps& assocMaps)
368 {
369 boost::container::flat_set<std::string> endpoints{endpoint};
370
371 addEndpointsToAssocIfaces(io, server, assocPath, endpoints, assocMaps);
372
373 AssociationPaths objects;
374 boost::container::flat_set e{endpoint};
375 objects.emplace(assocPath, e);
376
377 auto a = assocMaps.owners.find(ownerPath);
378 if (a != assocMaps.owners.end())
379 {
380 auto o = a->second.find(owner);
381 if (o != a->second.end())
382 {
383 auto p = o->second.find(assocPath);
384 if (p != o->second.end())
385 {
386 p->second.emplace(endpoint);
387 }
388 else
389 {
390 o->second.emplace(assocPath, e);
391 }
392 }
393 else
394 {
395 a->second.emplace(owner, std::move(objects));
396 }
397 }
398 else
399 {
400 boost::container::flat_map<std::string, AssociationPaths> owners;
401 owners.emplace(owner, std::move(objects));
402 assocMaps.owners.emplace(endpoint, owners);
403 }
404 }
405
checkIfPendingAssociation(boost::asio::io_context & io,const std::string & objectPath,const InterfaceMapType & interfaceMap,AssociationMaps & assocMaps,sdbusplus::asio::object_server & server)406 void checkIfPendingAssociation(
407 boost::asio::io_context& io, const std::string& objectPath,
408 const InterfaceMapType& interfaceMap, AssociationMaps& assocMaps,
409 sdbusplus::asio::object_server& server)
410 {
411 auto pending = assocMaps.pending.find(objectPath);
412 if (pending == assocMaps.pending.end())
413 {
414 return;
415 }
416
417 if (interfaceMap.find(objectPath) == interfaceMap.end())
418 {
419 return;
420 }
421
422 auto endpoint = pending->second.begin();
423
424 while (endpoint != pending->second.end())
425 {
426 const auto& e = std::get<assocPos>(*endpoint);
427
428 // Ensure the other side of the association still exists
429 if (interfaceMap.find(std::get<reversePathPos>(e)) ==
430 interfaceMap.end())
431 {
432 endpoint++;
433 continue;
434 }
435
436 // Add both sides of the association:
437 // objectPath/forwardType and reversePath/reverseType
438 //
439 // The ownerPath is the reversePath - i.e. the endpoint that
440 // is on D-Bus and owns the org.openbmc.Associations iface.
441 //
442 const auto& ownerPath = std::get<reversePathPos>(e);
443 const auto& owner = std::get<ownerPos>(*endpoint);
444
445 auto assocPath = objectPath + '/' + std::get<forwardTypePos>(e);
446 auto endpointPath = ownerPath;
447
448 try
449 {
450 addSingleAssociation(io, server, assocPath, endpointPath, owner,
451 ownerPath, assocMaps);
452
453 // Now the reverse direction (still the same owner and ownerPath)
454 assocPath = endpointPath + '/' + std::get<reverseTypePos>(e);
455 endpointPath = objectPath;
456 addSingleAssociation(io, server, assocPath, endpointPath, owner,
457 ownerPath, assocMaps);
458 }
459 catch (const sdbusplus::exception_t& e)
460 {
461 // In some case the interface could not be created on DBus and an
462 // exception is thrown. mapper has no control of the interface/path
463 // of the associations, so it has to catch the error and drop the
464 // association request.
465 std::cerr << "Error adding association: assocPath " << assocPath
466 << ", endpointPath " << endpointPath
467 << ", what: " << e.what() << "\n";
468 }
469
470 // Not pending anymore
471 endpoint = pending->second.erase(endpoint);
472 }
473
474 if (pending->second.empty())
475 {
476 assocMaps.pending.erase(objectPath);
477 }
478 }
479
findAssociations(const std::string & endpointPath,AssociationMaps & assocMaps,FindAssocResults & associationData)480 void findAssociations(const std::string& endpointPath,
481 AssociationMaps& assocMaps,
482 FindAssocResults& associationData)
483 {
484 for (const auto& [sourcePath, owners] : assocMaps.owners)
485 {
486 for (const auto& [owner, assocs] : owners)
487 {
488 for (const auto& [assocPath, endpoints] : assocs)
489 {
490 if (std::find(endpoints.begin(), endpoints.end(),
491 endpointPath) != endpoints.end())
492 {
493 // assocPath is <path>/<type> which tells us what is on the
494 // other side of the association.
495 auto pos = assocPath.rfind('/');
496 auto otherPath = assocPath.substr(0, pos);
497 auto otherType = assocPath.substr(pos + 1);
498
499 // Now we need to find the endpointPath/<type> ->
500 // [otherPath] entry so that we can get the type for
501 // endpointPath's side of the assoc. Do this by finding
502 // otherPath as an endpoint, and also checking for
503 // 'endpointPath/*' as the key.
504 auto a = std::find_if(
505 assocs.begin(), assocs.end(),
506 [&endpointPath, &otherPath](const auto& ap) {
507 const auto& endpoints = ap.second;
508 auto endpoint = std::find(
509 endpoints.begin(), endpoints.end(), otherPath);
510 if (endpoint != endpoints.end())
511 {
512 return ap.first.starts_with(endpointPath + '/');
513 }
514 return false;
515 });
516
517 if (a != assocs.end())
518 {
519 // Pull out the type from endpointPath/<type>
520 pos = a->first.rfind('/');
521 auto thisType = a->first.substr(pos + 1);
522
523 // Now we know the full association:
524 // endpointPath/thisType -> otherPath/otherType
525 Association association{thisType, otherType, otherPath};
526 associationData.emplace_back(owner, association);
527 }
528 }
529 }
530 }
531 }
532 }
533
534 /** @brief Remove an endpoint for a particular association from D-Bus.
535 *
536 * If the last endpoint is gone, remove the whole association interface,
537 * otherwise just update the D-Bus endpoints property.
538 *
539 * @param[in] assocPath - the association path
540 * @param[in] endpointPath - the endpoint path to find and remove
541 * @param[in,out] assocMaps - the association maps
542 * @param[in,out] server - sdbus system object
543 */
removeAssociationIfacesEntry(boost::asio::io_context & io,const std::string & assocPath,const std::string & endpointPath,AssociationMaps & assocMaps,sdbusplus::asio::object_server & server)544 void removeAssociationIfacesEntry(
545 boost::asio::io_context& io, const std::string& assocPath,
546 const std::string& endpointPath, AssociationMaps& assocMaps,
547 sdbusplus::asio::object_server& server)
548 {
549 auto assoc = assocMaps.ifaces.find(assocPath);
550 if (assoc != assocMaps.ifaces.end())
551 {
552 auto& endpoints = std::get<endpointsPos>(assoc->second);
553 auto e = std::find(endpoints.begin(), endpoints.end(), endpointPath);
554 if (e != endpoints.end())
555 {
556 endpoints.erase(e);
557
558 scheduleUpdateEndpointsOnDbus(io, server, assocPath, assocMaps);
559 }
560 }
561 }
562
563 /** @brief Remove an endpoint from the association owners map.
564 *
565 * For a specific association path and owner, remove the endpoint.
566 * Remove all remaining artifacts of that endpoint in the owners map
567 * based on what frees up after the erase.
568 *
569 * @param[in] assocPath - the association object path
570 * @param[in] endpointPath - the endpoint object path
571 * @param[in] owner - the owner of the association
572 * @param[in,out] assocMaps - the association maps
573 */
removeAssociationOwnersEntry(const std::string & assocPath,const std::string & endpointPath,const std::string & owner,AssociationMaps & assocMaps)574 void removeAssociationOwnersEntry(
575 const std::string& assocPath, const std::string& endpointPath,
576 const std::string& owner, AssociationMaps& assocMaps)
577 {
578 auto sources = assocMaps.owners.begin();
579 while (sources != assocMaps.owners.end())
580 {
581 auto owners = sources->second.find(owner);
582 if (owners != sources->second.end())
583 {
584 auto entry = owners->second.find(assocPath);
585 if (entry != owners->second.end())
586 {
587 auto e = std::find(entry->second.begin(), entry->second.end(),
588 endpointPath);
589 if (e != entry->second.end())
590 {
591 entry->second.erase(e);
592 if (entry->second.empty())
593 {
594 owners->second.erase(entry);
595 }
596 }
597 }
598
599 if (owners->second.empty())
600 {
601 sources->second.erase(owners);
602 }
603 }
604
605 if (sources->second.empty())
606 {
607 sources = assocMaps.owners.erase(sources);
608 continue;
609 }
610 sources++;
611 }
612 }
613
moveAssociationToPending(boost::asio::io_context & io,const std::string & endpointPath,AssociationMaps & assocMaps,sdbusplus::asio::object_server & server)614 void moveAssociationToPending(
615 boost::asio::io_context& io, const std::string& endpointPath,
616 AssociationMaps& assocMaps, sdbusplus::asio::object_server& server)
617 {
618 FindAssocResults associationData;
619
620 // Check which associations this path is an endpoint of, and
621 // then add them to the pending associations map and remove
622 // the associations objects.
623 findAssociations(endpointPath, assocMaps, associationData);
624
625 for (const auto& [owner, association] : associationData)
626 {
627 const auto& forwardPath = endpointPath;
628 const auto& forwardType = std::get<forwardTypePos>(association);
629 const auto& reversePath = std::get<reversePathPos>(association);
630 const auto& reverseType = std::get<reverseTypePos>(association);
631
632 addPendingAssociation(forwardPath, forwardType, reversePath,
633 reverseType, owner, assocMaps);
634
635 // Remove both sides of the association from assocMaps.ifaces
636 removeAssociationIfacesEntry(io, forwardPath + '/' + forwardType,
637 reversePath, assocMaps, server);
638 removeAssociationIfacesEntry(io, reversePath + '/' + reverseType,
639 forwardPath, assocMaps, server);
640
641 // Remove both sides of the association from assocMaps.owners
642 removeAssociationOwnersEntry(forwardPath + '/' + forwardType,
643 reversePath, owner, assocMaps);
644 removeAssociationOwnersEntry(reversePath + '/' + reverseType,
645 forwardPath, owner, assocMaps);
646 }
647 }
648