blob: 79672afdf7ae6006fb34a662b55a43de35b89082 [file] [log] [blame]
Andrew Geisslera80a3af2019-02-04 14:01:49 -06001#include "associations.hpp"
2
Matt Spinler7f8fd1f2019-04-08 15:21:59 -05003#include <boost/algorithm/string/predicate.hpp>
Lei YUb89c6612021-07-22 15:59:52 +08004#include <sdbusplus/exception.hpp>
Andrew Geissler4511b332019-02-21 15:40:40 -06005
Brad Bishop23520882022-05-26 21:39:53 -04006#include <iostream>
7
Andrew Geisslera80a3af2019-02-04 14:01:49 -06008void removeAssociation(const std::string& sourcePath, const std::string& owner,
9 sdbusplus::asio::object_server& server,
Matt Spinlere2359fb2019-04-05 14:11:33 -050010 AssociationMaps& assocMaps)
Andrew Geisslera80a3af2019-02-04 14:01:49 -060011{
12 // Use associationOwners to find the association paths and endpoints
13 // that the passed in object path and service own. Remove all of
14 // these endpoints from the actual association D-Bus objects, and if
15 // the endpoints property is then empty, the whole association object
16 // can be removed. Note there can be multiple services that own an
17 // association, and also that sourcePath is the path of the object
18 // that contains the org.openbmc.Associations interface and not the
19 // association path itself.
20
21 // Find the services that have associations for this object path
Matt Spinlere2359fb2019-04-05 14:11:33 -050022 auto owners = assocMaps.owners.find(sourcePath);
23 if (owners == assocMaps.owners.end())
Andrew Geisslera80a3af2019-02-04 14:01:49 -060024 {
25 return;
26 }
27
28 // Find the association paths and endpoints owned by this object
29 // path for this service.
30 auto assocs = owners->second.find(owner);
31 if (assocs == owners->second.end())
32 {
33 return;
34 }
35
36 for (const auto& [assocPath, endpointsToRemove] : assocs->second)
37 {
Andrew Geissler5629ae82019-02-21 12:59:09 -060038 removeAssociationEndpoints(server, assocPath, endpointsToRemove,
Matt Spinlere2359fb2019-04-05 14:11:33 -050039 assocMaps);
Andrew Geisslera80a3af2019-02-04 14:01:49 -060040 }
41
42 // Remove the associationOwners entries for this owning path/service.
43 owners->second.erase(assocs);
44 if (owners->second.empty())
45 {
Matt Spinlere2359fb2019-04-05 14:11:33 -050046 assocMaps.owners.erase(owners);
Andrew Geisslera80a3af2019-02-04 14:01:49 -060047 }
Matt Spinlercb9bcdb2019-04-08 10:58:49 -050048
49 // If we were still waiting on the other side of this association to
50 // show up, cancel that wait.
51 removeFromPendingAssociations(sourcePath, assocMaps);
Andrew Geisslera80a3af2019-02-04 14:01:49 -060052}
Andrew Geisslerff5ce922019-02-21 12:43:09 -060053
54void removeAssociationEndpoints(
55 sdbusplus::asio::object_server& objectServer, const std::string& assocPath,
56 const boost::container::flat_set<std::string>& endpointsToRemove,
Matt Spinlere2359fb2019-04-05 14:11:33 -050057 AssociationMaps& assocMaps)
Andrew Geisslerff5ce922019-02-21 12:43:09 -060058{
Matt Spinlere2359fb2019-04-05 14:11:33 -050059 auto assoc = assocMaps.ifaces.find(assocPath);
60 if (assoc == assocMaps.ifaces.end())
Andrew Geisslerff5ce922019-02-21 12:43:09 -060061 {
62 return;
63 }
64
65 auto& endpointsInDBus = std::get<endpointsPos>(assoc->second);
66
67 for (const auto& endpointToRemove : endpointsToRemove)
68 {
69 auto e = std::find(endpointsInDBus.begin(), endpointsInDBus.end(),
70 endpointToRemove);
71
72 if (e != endpointsInDBus.end())
73 {
74 endpointsInDBus.erase(e);
75 }
76 }
77
78 if (endpointsInDBus.empty())
79 {
80 objectServer.remove_interface(std::get<ifacePos>(assoc->second));
81 std::get<ifacePos>(assoc->second) = nullptr;
82 std::get<endpointsPos>(assoc->second).clear();
83 }
84 else
85 {
86 std::get<ifacePos>(assoc->second)
87 ->set_property("endpoints", endpointsInDBus);
88 }
89}
Andrew Geissler7f1c44d2019-02-21 13:44:16 -060090
91void checkAssociationEndpointRemoves(
92 const std::string& sourcePath, const std::string& owner,
93 const AssociationPaths& newAssociations,
Matt Spinlere2359fb2019-04-05 14:11:33 -050094 sdbusplus::asio::object_server& objectServer, AssociationMaps& assocMaps)
Andrew Geissler7f1c44d2019-02-21 13:44:16 -060095{
96 // Find the services that have associations on this path.
Matt Spinlere2359fb2019-04-05 14:11:33 -050097 auto originalOwners = assocMaps.owners.find(sourcePath);
98 if (originalOwners == assocMaps.owners.end())
Andrew Geissler7f1c44d2019-02-21 13:44:16 -060099 {
100 return;
101 }
102
103 // Find the associations for this service
104 auto originalAssociations = originalOwners->second.find(owner);
105 if (originalAssociations == originalOwners->second.end())
106 {
107 return;
108 }
109
110 // Compare the new endpoints versus the original endpoints, and
111 // remove any of the original ones that aren't in the new list.
112 for (const auto& [originalAssocPath, originalEndpoints] :
113 originalAssociations->second)
114 {
115 // Check if this source even still has each association that
116 // was there previously, and if not, remove all of its endpoints
117 // from the D-Bus endpoints property which will cause the whole
118 // association path to be removed if no endpoints remain.
119 auto newEndpoints = newAssociations.find(originalAssocPath);
120 if (newEndpoints == newAssociations.end())
121 {
122 removeAssociationEndpoints(objectServer, originalAssocPath,
Matt Spinlere2359fb2019-04-05 14:11:33 -0500123 originalEndpoints, assocMaps);
Andrew Geissler7f1c44d2019-02-21 13:44:16 -0600124 }
125 else
126 {
127 // The association is still there. Check if the endpoints
128 // changed.
129 boost::container::flat_set<std::string> toRemove;
130
Brad Bishop1f623802022-05-31 18:22:10 -0400131 for (const auto& originalEndpoint : originalEndpoints)
Andrew Geissler7f1c44d2019-02-21 13:44:16 -0600132 {
133 if (std::find(newEndpoints->second.begin(),
134 newEndpoints->second.end(),
135 originalEndpoint) == newEndpoints->second.end())
136 {
137 toRemove.emplace(originalEndpoint);
138 }
139 }
140 if (!toRemove.empty())
141 {
142 removeAssociationEndpoints(objectServer, originalAssocPath,
Matt Spinlere2359fb2019-04-05 14:11:33 -0500143 toRemove, assocMaps);
Andrew Geissler7f1c44d2019-02-21 13:44:16 -0600144 }
145 }
146 }
147}
Andrew Geissler4511b332019-02-21 15:40:40 -0600148
Matt Spinler11401e22019-04-08 13:13:25 -0500149void addEndpointsToAssocIfaces(
150 sdbusplus::asio::object_server& objectServer, const std::string& assocPath,
151 const boost::container::flat_set<std::string>& endpointPaths,
152 AssociationMaps& assocMaps)
153{
154 auto& iface = assocMaps.ifaces[assocPath];
155 auto& i = std::get<ifacePos>(iface);
156 auto& endpoints = std::get<endpointsPos>(iface);
157
158 // Only add new endpoints
Brad Bishop1f623802022-05-31 18:22:10 -0400159 for (const auto& e : endpointPaths)
Matt Spinler11401e22019-04-08 13:13:25 -0500160 {
161 if (std::find(endpoints.begin(), endpoints.end(), e) == endpoints.end())
162 {
163 endpoints.push_back(e);
164 }
165 }
166
167 // If the interface already exists, only need to update
168 // the property value, otherwise create it
169 if (i)
170 {
171 i->set_property("endpoints", endpoints);
172 }
173 else
174 {
Brad Bishopa098a372022-05-05 15:19:04 -0400175 i = objectServer.add_interface(assocPath, xyzAssociationInterface);
Matt Spinler11401e22019-04-08 13:13:25 -0500176 i->register_property("endpoints", endpoints);
177 i->initialize();
178 }
179}
180
Andrew Geissler4511b332019-02-21 15:40:40 -0600181void associationChanged(sdbusplus::asio::object_server& objectServer,
182 const std::vector<Association>& associations,
183 const std::string& path, const std::string& owner,
Brad Bishopa098a372022-05-05 15:19:04 -0400184 const InterfaceMapType& interfaceMap,
Matt Spinlere2359fb2019-04-05 14:11:33 -0500185 AssociationMaps& assocMaps)
Andrew Geissler4511b332019-02-21 15:40:40 -0600186{
187 AssociationPaths objects;
188
189 for (const Association& association : associations)
190 {
191 std::string forward;
192 std::string reverse;
Brad Bishop1f623802022-05-31 18:22:10 -0400193 std::string objectPath;
194 std::tie(forward, reverse, objectPath) = association;
Andrew Geissler4511b332019-02-21 15:40:40 -0600195
Brad Bishop1f623802022-05-31 18:22:10 -0400196 if (objectPath.empty())
Andrew Geissler0a560a52019-03-22 10:59:07 -0500197 {
198 std::cerr << "Found invalid association on path " << path << "\n";
199 continue;
200 }
Matt Spinlere0b0e3a2019-04-08 10:39:23 -0500201
202 // Can't create this association if the endpoint isn't on D-Bus.
Brad Bishop1f623802022-05-31 18:22:10 -0400203 if (interfaceMap.find(objectPath) == interfaceMap.end())
Matt Spinlere0b0e3a2019-04-08 10:39:23 -0500204 {
Brad Bishop1f623802022-05-31 18:22:10 -0400205 addPendingAssociation(objectPath, reverse, path, forward, owner,
Matt Spinlere0b0e3a2019-04-08 10:39:23 -0500206 assocMaps);
207 continue;
208 }
209
Brad Bishop1f623802022-05-31 18:22:10 -0400210 if (!forward.empty())
Andrew Geissler4511b332019-02-21 15:40:40 -0600211 {
Brad Bishop1f623802022-05-31 18:22:10 -0400212 objects[path + "/" + forward].emplace(objectPath);
Andrew Geissler4511b332019-02-21 15:40:40 -0600213 }
Brad Bishop1f623802022-05-31 18:22:10 -0400214 if (!reverse.empty())
Andrew Geissler4511b332019-02-21 15:40:40 -0600215 {
Brad Bishop1f623802022-05-31 18:22:10 -0400216 objects[objectPath + "/" + reverse].emplace(path);
Andrew Geissler4511b332019-02-21 15:40:40 -0600217 }
218 }
219 for (const auto& object : objects)
220 {
Matt Spinler11401e22019-04-08 13:13:25 -0500221 addEndpointsToAssocIfaces(objectServer, object.first, object.second,
222 assocMaps);
Andrew Geissler4511b332019-02-21 15:40:40 -0600223 }
224
225 // Check for endpoints being removed instead of added
226 checkAssociationEndpointRemoves(path, owner, objects, objectServer,
Matt Spinlere2359fb2019-04-05 14:11:33 -0500227 assocMaps);
Andrew Geissler4511b332019-02-21 15:40:40 -0600228
Matt Spinlere0b0e3a2019-04-08 10:39:23 -0500229 if (!objects.empty())
Andrew Geissler4511b332019-02-21 15:40:40 -0600230 {
Matt Spinlere0b0e3a2019-04-08 10:39:23 -0500231 // Update associationOwners with the latest info
232 auto a = assocMaps.owners.find(path);
233 if (a != assocMaps.owners.end())
Andrew Geissler4511b332019-02-21 15:40:40 -0600234 {
Matt Spinlere0b0e3a2019-04-08 10:39:23 -0500235 auto o = a->second.find(owner);
236 if (o != a->second.end())
237 {
238 o->second = std::move(objects);
239 }
240 else
241 {
242 a->second.emplace(owner, std::move(objects));
243 }
Andrew Geissler4511b332019-02-21 15:40:40 -0600244 }
245 else
246 {
Matt Spinlere0b0e3a2019-04-08 10:39:23 -0500247 boost::container::flat_map<std::string, AssociationPaths> owners;
248 owners.emplace(owner, std::move(objects));
249 assocMaps.owners.emplace(path, owners);
Andrew Geissler4511b332019-02-21 15:40:40 -0600250 }
251 }
Matt Spinlere0b0e3a2019-04-08 10:39:23 -0500252}
253
254void addPendingAssociation(const std::string& objectPath,
255 const std::string& type,
256 const std::string& endpointPath,
257 const std::string& endpointType,
258 const std::string& owner, AssociationMaps& assocMaps)
259{
260 Association assoc{type, endpointType, endpointPath};
261
262 auto p = assocMaps.pending.find(objectPath);
263 if (p == assocMaps.pending.end())
264 {
265 ExistingEndpoints ee;
266 ee.emplace_back(owner, std::move(assoc));
267 assocMaps.pending.emplace(objectPath, std::move(ee));
268 }
Andrew Geissler4511b332019-02-21 15:40:40 -0600269 else
270 {
Matt Spinlere0b0e3a2019-04-08 10:39:23 -0500271 // Already waiting on this path for another association,
272 // so just add this endpoint and owner.
273 auto& endpoints = p->second;
274 auto e =
275 std::find_if(endpoints.begin(), endpoints.end(),
276 [&assoc, &owner](const auto& endpoint) {
277 return (std::get<ownerPos>(endpoint) == owner) &&
278 (std::get<assocPos>(endpoint) == assoc);
279 });
280 if (e == endpoints.end())
281 {
282 endpoints.emplace_back(owner, std::move(assoc));
283 }
Andrew Geissler4511b332019-02-21 15:40:40 -0600284 }
285}
Matt Spinlercb9bcdb2019-04-08 10:58:49 -0500286
287void removeFromPendingAssociations(const std::string& endpointPath,
288 AssociationMaps& assocMaps)
289{
290 auto assoc = assocMaps.pending.begin();
291 while (assoc != assocMaps.pending.end())
292 {
293 auto endpoint = assoc->second.begin();
294 while (endpoint != assoc->second.end())
295 {
296 auto& e = std::get<assocPos>(*endpoint);
297 if (std::get<reversePathPos>(e) == endpointPath)
298 {
299 endpoint = assoc->second.erase(endpoint);
300 continue;
301 }
302
303 endpoint++;
304 }
305
306 if (assoc->second.empty())
307 {
308 assoc = assocMaps.pending.erase(assoc);
309 continue;
310 }
311
312 assoc++;
313 }
314}
Matt Spinler11401e22019-04-08 13:13:25 -0500315
316void addSingleAssociation(sdbusplus::asio::object_server& server,
317 const std::string& assocPath,
318 const std::string& endpoint, const std::string& owner,
319 const std::string& ownerPath,
320 AssociationMaps& assocMaps)
321{
322 boost::container::flat_set<std::string> endpoints{endpoint};
323
324 addEndpointsToAssocIfaces(server, assocPath, endpoints, assocMaps);
325
326 AssociationPaths objects;
327 boost::container::flat_set e{endpoint};
328 objects.emplace(assocPath, e);
329
330 auto a = assocMaps.owners.find(ownerPath);
331 if (a != assocMaps.owners.end())
332 {
333 auto o = a->second.find(owner);
334 if (o != a->second.end())
335 {
336 auto p = o->second.find(assocPath);
337 if (p != o->second.end())
338 {
339 p->second.emplace(endpoint);
340 }
341 else
342 {
343 o->second.emplace(assocPath, e);
344 }
345 }
346 else
347 {
348 a->second.emplace(owner, std::move(objects));
349 }
350 }
351 else
352 {
353 boost::container::flat_map<std::string, AssociationPaths> owners;
354 owners.emplace(owner, std::move(objects));
355 assocMaps.owners.emplace(endpoint, owners);
356 }
357}
358
359void checkIfPendingAssociation(const std::string& objectPath,
Brad Bishopa098a372022-05-05 15:19:04 -0400360 const InterfaceMapType& interfaceMap,
Matt Spinler11401e22019-04-08 13:13:25 -0500361 AssociationMaps& assocMaps,
362 sdbusplus::asio::object_server& server)
363{
364 auto pending = assocMaps.pending.find(objectPath);
365 if (pending == assocMaps.pending.end())
366 {
367 return;
368 }
369
370 if (interfaceMap.find(objectPath) == interfaceMap.end())
371 {
372 return;
373 }
374
375 auto endpoint = pending->second.begin();
376
377 while (endpoint != pending->second.end())
378 {
379 const auto& e = std::get<assocPos>(*endpoint);
380
381 // Ensure the other side of the association still exists
382 if (interfaceMap.find(std::get<reversePathPos>(e)) ==
383 interfaceMap.end())
384 {
385 endpoint++;
386 continue;
387 }
388
389 // Add both sides of the association:
390 // objectPath/forwardType and reversePath/reverseType
391 //
392 // The ownerPath is the reversePath - i.e. the endpoint that
393 // is on D-Bus and owns the org.openbmc.Associations iface.
394 //
395 const auto& ownerPath = std::get<reversePathPos>(e);
396 const auto& owner = std::get<ownerPos>(*endpoint);
397
398 auto assocPath = objectPath + '/' + std::get<forwardTypePos>(e);
399 auto endpointPath = ownerPath;
400
Lei YUb89c6612021-07-22 15:59:52 +0800401 try
402 {
403 addSingleAssociation(server, assocPath, endpointPath, owner,
404 ownerPath, assocMaps);
Matt Spinler11401e22019-04-08 13:13:25 -0500405
Lei YUb89c6612021-07-22 15:59:52 +0800406 // Now the reverse direction (still the same owner and ownerPath)
407 assocPath = endpointPath + '/' + std::get<reverseTypePos>(e);
408 endpointPath = objectPath;
409 addSingleAssociation(server, assocPath, endpointPath, owner,
410 ownerPath, assocMaps);
411 }
Patrick Williamscc8070b2022-07-22 19:26:55 -0500412 catch (const sdbusplus::exception_t& e)
Lei YUb89c6612021-07-22 15:59:52 +0800413 {
414 // In some case the interface could not be created on DBus and an
415 // exception is thrown. mapper has no control of the interface/path
416 // of the associations, so it has to catch the error and drop the
417 // association request.
Brad Bishop1f623802022-05-31 18:22:10 -0400418 std::cerr << "Error adding association: assocPath " << assocPath
419 << ", endpointPath " << endpointPath
420 << ", what: " << e.what() << "\n";
Lei YUb89c6612021-07-22 15:59:52 +0800421 }
Matt Spinler11401e22019-04-08 13:13:25 -0500422
423 // Not pending anymore
424 endpoint = pending->second.erase(endpoint);
425 }
426
427 if (pending->second.empty())
428 {
429 assocMaps.pending.erase(objectPath);
430 }
431}
Matt Spinler7f8fd1f2019-04-08 15:21:59 -0500432
433void findAssociations(const std::string& endpointPath,
434 AssociationMaps& assocMaps,
435 FindAssocResults& associationData)
436{
437 for (const auto& [sourcePath, owners] : assocMaps.owners)
438 {
439 for (const auto& [owner, assocs] : owners)
440 {
441 for (const auto& [assocPath, endpoints] : assocs)
442 {
443 if (std::find(endpoints.begin(), endpoints.end(),
444 endpointPath) != endpoints.end())
445 {
446 // assocPath is <path>/<type> which tells us what is on the
447 // other side of the association.
448 auto pos = assocPath.rfind('/');
449 auto otherPath = assocPath.substr(0, pos);
450 auto otherType = assocPath.substr(pos + 1);
451
452 // Now we need to find the endpointPath/<type> ->
453 // [otherPath] entry so that we can get the type for
454 // endpointPath's side of the assoc. Do this by finding
455 // otherPath as an endpoint, and also checking for
456 // 'endpointPath/*' as the key.
457 auto a = std::find_if(
458 assocs.begin(), assocs.end(),
459 [&endpointPath, &otherPath](const auto& ap) {
460 const auto& endpoints = ap.second;
461 auto endpoint = std::find(
462 endpoints.begin(), endpoints.end(), otherPath);
463 if (endpoint != endpoints.end())
464 {
465 return boost::starts_with(ap.first,
466 endpointPath + '/');
467 }
468 return false;
469 });
470
471 if (a != assocs.end())
472 {
473 // Pull out the type from endpointPath/<type>
474 pos = a->first.rfind('/');
475 auto thisType = a->first.substr(pos + 1);
476
477 // Now we know the full association:
478 // endpointPath/thisType -> otherPath/otherType
479 Association association{thisType, otherType, otherPath};
480 associationData.emplace_back(owner, association);
481 }
482 }
483 }
484 }
485 }
486}
Matt Spinler9c3d2852019-04-08 15:57:19 -0500487
488/** @brief Remove an endpoint for a particular association from D-Bus.
489 *
490 * If the last endpoint is gone, remove the whole association interface,
491 * otherwise just update the D-Bus endpoints property.
492 *
493 * @param[in] assocPath - the association path
494 * @param[in] endpointPath - the endpoint path to find and remove
495 * @param[in,out] assocMaps - the association maps
496 * @param[in,out] server - sdbus system object
497 */
498void removeAssociationIfacesEntry(const std::string& assocPath,
499 const std::string& endpointPath,
500 AssociationMaps& assocMaps,
501 sdbusplus::asio::object_server& server)
502{
503 auto assoc = assocMaps.ifaces.find(assocPath);
504 if (assoc != assocMaps.ifaces.end())
505 {
506 auto& endpoints = std::get<endpointsPos>(assoc->second);
507 auto e = std::find(endpoints.begin(), endpoints.end(), endpointPath);
508 if (e != endpoints.end())
509 {
510 endpoints.erase(e);
511
512 if (endpoints.empty())
513 {
514 server.remove_interface(std::get<ifacePos>(assoc->second));
515 std::get<ifacePos>(assoc->second) = nullptr;
516 }
517 else
518 {
519 std::get<ifacePos>(assoc->second)
520 ->set_property("endpoints", endpoints);
521 }
522 }
523 }
524}
525
526/** @brief Remove an endpoint from the association owners map.
527 *
528 * For a specific association path and owner, remove the endpoint.
529 * Remove all remaining artifacts of that endpoint in the owners map
530 * based on what frees up after the erase.
531 *
532 * @param[in] assocPath - the association object path
533 * @param[in] endpointPath - the endpoint object path
534 * @param[in] owner - the owner of the association
535 * @param[in,out] assocMaps - the association maps
Matt Spinler9c3d2852019-04-08 15:57:19 -0500536 */
537void removeAssociationOwnersEntry(const std::string& assocPath,
538 const std::string& endpointPath,
539 const std::string& owner,
Brad Bishopa6646902022-06-02 19:05:34 -0400540 AssociationMaps& assocMaps)
Matt Spinler9c3d2852019-04-08 15:57:19 -0500541{
542 auto sources = assocMaps.owners.begin();
543 while (sources != assocMaps.owners.end())
544 {
545 auto owners = sources->second.find(owner);
546 if (owners != sources->second.end())
547 {
548 auto entry = owners->second.find(assocPath);
549 if (entry != owners->second.end())
550 {
551 auto e = std::find(entry->second.begin(), entry->second.end(),
552 endpointPath);
553 if (e != entry->second.end())
554 {
555 entry->second.erase(e);
556 if (entry->second.empty())
557 {
558 owners->second.erase(entry);
559 }
560 }
561 }
562
563 if (owners->second.empty())
564 {
565 sources->second.erase(owners);
566 }
567 }
568
569 if (sources->second.empty())
570 {
571 sources = assocMaps.owners.erase(sources);
572 continue;
573 }
574 sources++;
575 }
576}
577
578void moveAssociationToPending(const std::string& endpointPath,
579 AssociationMaps& assocMaps,
580 sdbusplus::asio::object_server& server)
581{
582 FindAssocResults associationData;
583
584 // Check which associations this path is an endpoint of, and
585 // then add them to the pending associations map and remove
586 // the associations objects.
587 findAssociations(endpointPath, assocMaps, associationData);
588
589 for (const auto& [owner, association] : associationData)
590 {
591 const auto& forwardPath = endpointPath;
592 const auto& forwardType = std::get<forwardTypePos>(association);
593 const auto& reversePath = std::get<reversePathPos>(association);
594 const auto& reverseType = std::get<reverseTypePos>(association);
595
596 addPendingAssociation(forwardPath, forwardType, reversePath,
597 reverseType, owner, assocMaps);
598
599 // Remove both sides of the association from assocMaps.ifaces
600 removeAssociationIfacesEntry(forwardPath + '/' + forwardType,
601 reversePath, assocMaps, server);
602 removeAssociationIfacesEntry(reversePath + '/' + reverseType,
603 forwardPath, assocMaps, server);
604
605 // Remove both sides of the association from assocMaps.owners
606 removeAssociationOwnersEntry(forwardPath + '/' + forwardType,
Brad Bishopa6646902022-06-02 19:05:34 -0400607 reversePath, owner, assocMaps);
Matt Spinler9c3d2852019-04-08 15:57:19 -0500608 removeAssociationOwnersEntry(reversePath + '/' + reverseType,
Brad Bishopa6646902022-06-02 19:05:34 -0400609 forwardPath, owner, assocMaps);
Matt Spinler9c3d2852019-04-08 15:57:19 -0500610 }
611}