1 #include "src/associations.hpp" 2 3 #include "src/test/util/asio_server_class.hpp" 4 #include "src/test/util/association_objects.hpp" 5 #include "src/test/util/debug_output.hpp" 6 7 #include <sdbusplus/asio/connection.hpp> 8 #include <sdbusplus/asio/object_server.hpp> 9 10 #include <gtest/gtest.h> 11 12 class TestAssociations : public AsioServerClassTest 13 { 14 public: 15 boost::asio::io_context io; 16 virtual void SetUp() 17 { 18 io.run(); 19 } 20 }; 21 sdbusplus::asio::object_server* TestAssociations::AsioServerClassTest::server = 22 nullptr; 23 24 // Verify call when path is not in associated owners 25 TEST_F(TestAssociations, SourcePathNotInAssociations) 26 { 27 EXPECT_NE(nullptr, server); 28 std::string sourcePath = "/xyz/openbmc_project/no/association"; 29 AssociationMaps assocMaps; 30 31 removeAssociation(io, sourcePath, defaultDbusSvc, *server, assocMaps); 32 } 33 34 // Verify call when owner is not in associated owners 35 TEST_F(TestAssociations, OwnerNotInAssociations) 36 { 37 AssociationMaps assocMaps; 38 assocMaps.owners = createDefaultOwnerAssociation(); 39 40 removeAssociation(io, defaultSourcePath, defaultDbusSvc, *server, 41 assocMaps); 42 } 43 44 // Verify call when path is not in associated interfaces 45 TEST_F(TestAssociations, PathNotInAssocInterfaces) 46 { 47 AssociationMaps assocMaps; 48 49 assocMaps.owners = createDefaultOwnerAssociation(); 50 51 removeAssociation(io, defaultSourcePath, defaultDbusSvc, *server, 52 assocMaps); 53 54 EXPECT_TRUE(assocMaps.owners.empty()); 55 } 56 57 // Verify call when path is in associated interfaces 58 TEST_F(TestAssociations, PathIsInAssociatedInterfaces) 59 { 60 // Build up these objects so that an associated interface will match 61 // with the associated owner being removed 62 AssociationMaps assocMaps; 63 assocMaps.owners = createDefaultOwnerAssociation(); 64 assocMaps.ifaces = createDefaultInterfaceAssociation(server); 65 66 removeAssociation(io, defaultSourcePath, defaultDbusSvc, *server, 67 assocMaps); 68 69 // Verify owner association was deleted 70 EXPECT_TRUE(assocMaps.owners.empty()); 71 72 // Verify endpoint was deleted from interface association 73 auto intfEndpoints = 74 std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]); 75 EXPECT_EQ(intfEndpoints.size(), 0); 76 intfEndpoints = std::get<endpointsPos>(assocMaps.ifaces[defaultRevPath]); 77 EXPECT_EQ(intfEndpoints.size(), 0); 78 } 79 80 // Verify call when path is in associated interfaces, with extra endpoints 81 TEST_F(TestAssociations, PathIsInAssociatedInterfacesExtraEndpoints) 82 { 83 // Build up these objects so that an associated interface will match 84 // with the associated owner being removed 85 AssociationMaps assocMaps; 86 assocMaps.owners = createDefaultOwnerAssociation(); 87 assocMaps.ifaces = createDefaultInterfaceAssociation(server); 88 89 // Add another endpoint to the assoc interfaces 90 addEndpointToInterfaceAssociation(assocMaps.ifaces); 91 92 removeAssociation(io, defaultSourcePath, defaultDbusSvc, *server, 93 assocMaps); 94 95 // Verify owner association was deleted 96 EXPECT_TRUE(assocMaps.owners.empty()); 97 98 // Verify all endpoints are deleted since source path was deleted 99 auto intfEndpoints = 100 std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]); 101 EXPECT_EQ(intfEndpoints.size(), 0); 102 intfEndpoints = std::get<endpointsPos>(assocMaps.ifaces[defaultRevPath]); 103 EXPECT_EQ(intfEndpoints.size(), 0); 104 } 105 106 // Verify no associations or endpoints removed when the change is identical 107 TEST_F(TestAssociations, checkAssociationEndpointRemovesNoEpRemove) 108 { 109 AssociationPaths newAssocPaths = {{defaultFwdPath, {defaultEndpoint}}, 110 {defaultRevPath, {defaultSourcePath}}}; 111 112 AssociationMaps assocMaps; 113 assocMaps.owners = createDefaultOwnerAssociation(); 114 assocMaps.ifaces = createDefaultInterfaceAssociation(server); 115 116 checkAssociationEndpointRemoves(io, defaultSourcePath, defaultDbusSvc, 117 newAssocPaths, *server, assocMaps); 118 119 // Verify endpoints were not deleted because they matche with what was 120 // in the original 121 auto intfEndpoints = 122 std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]); 123 EXPECT_EQ(intfEndpoints.size(), 1); 124 intfEndpoints = std::get<endpointsPos>(assocMaps.ifaces[defaultRevPath]); 125 EXPECT_EQ(intfEndpoints.size(), 1); 126 } 127 128 // Verify endpoint is removed when assoc path is different 129 TEST_F(TestAssociations, checkAssociationEndpointRemovesEpRemoveApDiff) 130 { 131 AssociationPaths newAssocPaths = {{"/different/path", {defaultEndpoint}}}; 132 133 AssociationMaps assocMaps; 134 assocMaps.owners = createDefaultOwnerAssociation(); 135 assocMaps.ifaces = createDefaultInterfaceAssociation(server); 136 137 checkAssociationEndpointRemoves(io, defaultSourcePath, defaultDbusSvc, 138 newAssocPaths, *server, assocMaps); 139 140 // Verify initial endpoints were deleted because the new path 141 auto intfEndpoints = 142 std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]); 143 EXPECT_EQ(intfEndpoints.size(), 0); 144 intfEndpoints = std::get<endpointsPos>(assocMaps.ifaces[defaultRevPath]); 145 EXPECT_EQ(intfEndpoints.size(), 0); 146 } 147 148 // Verify endpoint is removed when endpoint is different 149 TEST_F(TestAssociations, checkAssociationEndpointRemovesEpRemoveEpChanged) 150 { 151 AssociationPaths newAssocPaths = { 152 {defaultFwdPath, {defaultEndpoint + "/different"}}, 153 {defaultRevPath, {defaultSourcePath + "/different"}}}; 154 155 AssociationMaps assocMaps; 156 assocMaps.owners = createDefaultOwnerAssociation(); 157 assocMaps.ifaces = createDefaultInterfaceAssociation(server); 158 159 checkAssociationEndpointRemoves(io, defaultSourcePath, defaultDbusSvc, 160 newAssocPaths, *server, assocMaps); 161 162 // Verify initial endpoints were deleted because of different endpoints 163 auto intfEndpoints = 164 std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]); 165 EXPECT_EQ(intfEndpoints.size(), 0); 166 intfEndpoints = std::get<endpointsPos>(assocMaps.ifaces[defaultRevPath]); 167 EXPECT_EQ(intfEndpoints.size(), 0); 168 } 169 170 // Verify existing endpoint deleted when empty endpoint is provided 171 TEST_F(TestAssociations, associationChangedEmptyEndpoint) 172 { 173 std::vector<Association> associations = { 174 {"inventory_cee", "error_cee", ""}}; 175 InterfaceMapType interfaceMap; 176 177 AssociationMaps assocMaps; 178 assocMaps.owners = createDefaultOwnerAssociation(); 179 assocMaps.ifaces = createDefaultInterfaceAssociation(server); 180 181 // Empty endpoint will result in deletion of corresponding assocInterface 182 associationChanged(io, *server, associations, defaultSourcePath, 183 defaultDbusSvc, interfaceMap, assocMaps); 184 185 // Both of these should be 0 since we have an invalid endpoint 186 auto intfEndpoints = 187 std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]); 188 EXPECT_EQ(intfEndpoints.size(), 0); 189 intfEndpoints = std::get<endpointsPos>(assocMaps.ifaces[defaultRevPath]); 190 EXPECT_EQ(intfEndpoints.size(), 0); 191 192 EXPECT_EQ(assocMaps.pending.size(), 0); 193 } 194 195 // Add a new association with endpoint 196 TEST_F(TestAssociations, associationChangedAddNewAssoc) 197 { 198 std::vector<Association> associations = { 199 {"abc", "def", "/xyz/openbmc_project/new/endpoint"}}; 200 201 AssociationMaps assocMaps; 202 assocMaps.owners = createDefaultOwnerAssociation(); 203 assocMaps.ifaces = createDefaultInterfaceAssociation(server); 204 205 // Make it look like the assoc endpoints are on D-Bus 206 InterfaceMapType interfaceMap = { 207 {"/new/source/path", {{defaultDbusSvc, {"a"}}}}, 208 {"/xyz/openbmc_project/new/endpoint", {{defaultDbusSvc, {"a"}}}}}; 209 210 associationChanged(io, *server, associations, "/new/source/path", 211 defaultDbusSvc, interfaceMap, assocMaps); 212 213 // Two source paths 214 EXPECT_EQ(assocMaps.owners.size(), 2); 215 216 // Four interfaces 217 EXPECT_EQ(assocMaps.ifaces.size(), 4); 218 219 // Nothing pending 220 EXPECT_EQ(assocMaps.pending.size(), 0); 221 222 // New endpoint so assocMaps.ifaces should be same size 223 auto intfEndpoints = 224 std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]); 225 EXPECT_EQ(intfEndpoints.size(), 1); 226 } 227 228 // Add a new association to empty objects 229 TEST_F(TestAssociations, associationChangedAddNewAssocEmptyObj) 230 { 231 std::string sourcePath = "/logging/entry/1"; 232 std::string owner = "xyz.openbmc_project.Test"; 233 std::vector<Association> associations = { 234 {"inventory_canaeo", "error_canaeo", 235 "/xyz/openbmc_project/inventory/system/chassis"}}; 236 237 // Empty objects because this test will ensure assocOwners adds the 238 // changed association and interface 239 AssociationMaps assocMaps; 240 241 // Make it look like the assoc endpoints are on D-Bus 242 InterfaceMapType interfaceMap = createDefaultInterfaceMap(); 243 244 associationChanged(io, *server, associations, defaultSourcePath, 245 defaultDbusSvc, interfaceMap, assocMaps); 246 247 // New associations so ensure it now contains a single entry 248 EXPECT_EQ(assocMaps.owners.size(), 1); 249 250 // Nothing pending 251 EXPECT_EQ(assocMaps.pending.size(), 0); 252 253 // Verify corresponding assoc paths each have one endpoint in assoc 254 // interfaces and that those endpoints match 255 auto singleOwner = assocMaps.owners[defaultSourcePath]; 256 auto singleIntf = singleOwner[defaultDbusSvc]; 257 for (auto i : singleIntf) 258 { 259 auto intfEndpoints = std::get<endpointsPos>(assocMaps.ifaces[i.first]); 260 EXPECT_EQ(intfEndpoints.size(), 1); 261 EXPECT_EQ(intfEndpoints[0], *i.second.begin()); 262 } 263 } 264 265 // Add a new association to same source path but with new owner 266 TEST_F(TestAssociations, associationChangedAddNewAssocNewOwner) 267 { 268 std::string newOwner = "xyz.openbmc_project.Test2"; 269 std::vector<Association> associations = { 270 {"inventory_canano", "error_canano", 271 "/xyz/openbmc_project/inventory/system/chassis"}}; 272 273 // Make it look like the assoc endpoints are on D-Bus 274 InterfaceMapType interfaceMap = createDefaultInterfaceMap(); 275 276 AssociationMaps assocMaps; 277 assocMaps.owners = createDefaultOwnerAssociation(); 278 assocMaps.ifaces = createDefaultInterfaceAssociation(server); 279 280 associationChanged(io, *server, associations, defaultSourcePath, newOwner, 281 interfaceMap, assocMaps); 282 283 // New endpoint so assocOwners should be same size 284 EXPECT_EQ(assocMaps.owners.size(), 1); 285 286 // Ensure only one endpoint under first path 287 auto intfEndpoints = 288 std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]); 289 EXPECT_EQ(intfEndpoints.size(), 1); 290 291 // Ensure the 2 new association endpoints are under the new owner 292 auto a = assocMaps.owners.find(defaultSourcePath); 293 auto o = a->second.find(newOwner); 294 EXPECT_EQ(o->second.size(), 2); 295 296 // Nothing pending 297 EXPECT_EQ(assocMaps.pending.size(), 0); 298 } 299 300 // Add a new association to existing interface path 301 TEST_F(TestAssociations, associationChangedAddNewAssocSameInterface) 302 { 303 std::vector<Association> associations = { 304 {"abc", "error", "/xyz/openbmc_project/inventory/system/chassis"}}; 305 306 // Make it look like the assoc endpoints are on D-Bus 307 InterfaceMapType interfaceMap = createDefaultInterfaceMap(); 308 309 AssociationMaps assocMaps; 310 assocMaps.owners = createDefaultOwnerAssociation(); 311 assocMaps.ifaces = createDefaultInterfaceAssociation(server); 312 313 associationChanged(io, *server, associations, defaultSourcePath, 314 defaultDbusSvc, interfaceMap, assocMaps); 315 316 // Should have 3 entries in AssociationInterfaces, one is just missing an 317 // endpoint 318 EXPECT_EQ(assocMaps.ifaces.size(), 3); 319 320 // Change to existing interface so it will be removed here 321 auto intfEndpoints = 322 std::get<endpointsPos>(assocMaps.ifaces[defaultFwdPath]); 323 EXPECT_EQ(intfEndpoints.size(), 0); 324 325 // The new endpoint should exist though in it's place 326 intfEndpoints = std::get<endpointsPos>( 327 assocMaps.ifaces[defaultSourcePath + "/" + "abc"]); 328 EXPECT_EQ(intfEndpoints.size(), 1); 329 330 // Added to an existing owner path so still 1 331 EXPECT_EQ(assocMaps.owners.size(), 1); 332 333 EXPECT_EQ(assocMaps.pending.size(), 0); 334 } 335 336 // Add 2 pending associations 337 TEST_F(TestAssociations, addPendingAssocs) 338 { 339 AssociationMaps assocMaps; 340 341 addPendingAssociation(defaultSourcePath, "inventory", defaultEndpoint, 342 "error", defaultDbusSvc, assocMaps); 343 344 EXPECT_TRUE(assocMaps.ifaces.empty()); 345 EXPECT_TRUE(assocMaps.owners.empty()); 346 347 EXPECT_EQ(assocMaps.pending.size(), 1); 348 349 addPendingAssociation("some/other/path", "inventory", defaultEndpoint, 350 "error", defaultDbusSvc, assocMaps); 351 352 EXPECT_TRUE(assocMaps.ifaces.empty()); 353 EXPECT_TRUE(assocMaps.owners.empty()); 354 355 EXPECT_EQ(assocMaps.pending.size(), 2); 356 } 357 358 // Test adding a new endpoint to a pending association 359 TEST_F(TestAssociations, addPendingAssocsNewEndpoints) 360 { 361 AssociationMaps assocMaps; 362 363 addPendingAssociation(defaultSourcePath, "inventory", defaultEndpoint, 364 "error", defaultDbusSvc, assocMaps); 365 366 EXPECT_EQ(assocMaps.pending.size(), 1); 367 368 addPendingAssociation(defaultSourcePath, "inventory", "some/other/endpoint", 369 "error", defaultDbusSvc, assocMaps); 370 371 // Same pending path, so still just 1 entry 372 EXPECT_EQ(assocMaps.pending.size(), 1); 373 374 auto assoc = assocMaps.pending.find(defaultSourcePath); 375 EXPECT_NE(assoc, assocMaps.pending.end()); 376 377 auto& endpoints = assoc->second; 378 EXPECT_EQ(endpoints.size(), 2); 379 } 380 381 // Test adding a new owner to a pending association 382 TEST_F(TestAssociations, addPendingAssocsNewOwner) 383 { 384 AssociationMaps assocMaps; 385 386 addPendingAssociation(defaultSourcePath, "inventory", defaultEndpoint, 387 "error", defaultDbusSvc, assocMaps); 388 389 EXPECT_EQ(assocMaps.pending.size(), 1); 390 391 addPendingAssociation(defaultSourcePath, "inventory", defaultEndpoint, 392 "error", "new owner", assocMaps); 393 394 EXPECT_EQ(assocMaps.pending.size(), 1); 395 396 auto assoc = assocMaps.pending.find(defaultSourcePath); 397 EXPECT_NE(assoc, assocMaps.pending.end()); 398 399 auto& endpoints = assoc->second; 400 EXPECT_EQ(endpoints.size(), 2); 401 } 402 403 // Add a pending association inside associationChanged 404 TEST_F(TestAssociations, associationChangedPending) 405 { 406 std::vector<Association> associations = { 407 {"abc", "def", "/xyz/openbmc_project/new/endpoint"}}; 408 409 AssociationMaps assocMaps; 410 InterfaceMapType interfaceMap; 411 412 associationChanged(io, *server, associations, "/new/source/path", 413 defaultDbusSvc, interfaceMap, assocMaps); 414 415 // No associations were actually added 416 EXPECT_EQ(assocMaps.owners.size(), 0); 417 EXPECT_EQ(assocMaps.ifaces.size(), 0); 418 419 // 1 pending association 420 EXPECT_EQ(assocMaps.pending.size(), 1); 421 } 422 423 // Test removing pending associations 424 TEST_F(TestAssociations, testRemoveFromPendingAssociations) 425 { 426 AssociationMaps assocMaps; 427 428 addPendingAssociation(defaultSourcePath, "inventory", defaultEndpoint, 429 "error", defaultDbusSvc, assocMaps); 430 431 addPendingAssociation(defaultSourcePath, "inventory", "some/other/endpoint", 432 "error", defaultDbusSvc, assocMaps); 433 434 EXPECT_EQ(assocMaps.pending.size(), 1); 435 436 removeFromPendingAssociations("some/other/endpoint", assocMaps); 437 438 // Still 1 pending entry, but down to 1 endpoint 439 EXPECT_EQ(assocMaps.pending.size(), 1); 440 441 auto assoc = assocMaps.pending.find(defaultSourcePath); 442 EXPECT_NE(assoc, assocMaps.pending.end()); 443 auto& endpoints = assoc->second; 444 EXPECT_EQ(endpoints.size(), 1); 445 446 // Now nothing pending 447 removeFromPendingAssociations(defaultEndpoint, assocMaps); 448 EXPECT_EQ(assocMaps.pending.size(), 0); 449 } 450 451 // Test moving a pending association to a real one 452 TEST_F(TestAssociations, checkIfPending) 453 { 454 AssociationMaps assocMaps; 455 InterfaceMapType interfaceMap = { 456 {defaultSourcePath, {{defaultDbusSvc, {"a"}}}}, 457 {defaultEndpoint, {{defaultDbusSvc, {"b"}}}}}; 458 459 addPendingAssociation(defaultSourcePath, "inventory_cip", defaultEndpoint, 460 "error_cip", defaultDbusSvc, assocMaps); 461 EXPECT_EQ(assocMaps.pending.size(), 1); 462 463 // Move the pending association to a real association 464 checkIfPendingAssociation(io, defaultSourcePath, interfaceMap, assocMaps, 465 *server); 466 467 EXPECT_TRUE(assocMaps.pending.empty()); 468 EXPECT_EQ(assocMaps.owners.size(), 1); 469 EXPECT_EQ(assocMaps.ifaces.size(), 2); 470 471 // This shouldn't do anything, since /new/path isn't pending 472 checkIfPendingAssociation(io, "/new/path", interfaceMap, assocMaps, 473 *server); 474 EXPECT_TRUE(assocMaps.pending.empty()); 475 EXPECT_EQ(assocMaps.owners.size(), 1); 476 EXPECT_EQ(assocMaps.ifaces.size(), 2); 477 } 478 479 TEST_F(TestAssociations, findAssociations) 480 { 481 std::vector<std::tuple<std::string, Association>> associationData; 482 AssociationMaps assocMaps; 483 484 assocMaps.owners = { 485 {"pathA", 486 {{"ownerA", 487 {{"pathA/typeA", {"endpointA", "endpointB"}}, 488 {"endpointA/type0", {"pathA"}}}}}}, 489 490 {"pathJ", 491 {{"ownerC", 492 {{"pathJ/typeA", {"endpointF"}}, {"endpointF/type0", {"pathJ"}}}}}}, 493 494 {"pathX", 495 {{"ownerB", 496 {{"pathX/typeB", {"endpointA"}}, {"endpointA/type1", {"pathX"}}}}}}}; 497 498 findAssociations("endpointA", assocMaps, associationData); 499 ASSERT_EQ(associationData.size(), 2); 500 501 { 502 auto ad = std::find_if( 503 associationData.begin(), associationData.end(), 504 [](const auto& ad) { return std::get<0>(ad) == "ownerA"; }); 505 ASSERT_NE(ad, associationData.end()); 506 507 auto& a = std::get<1>(*ad); 508 ASSERT_EQ(std::get<0>(a), "type0"); 509 ASSERT_EQ(std::get<1>(a), "typeA"); 510 ASSERT_EQ(std::get<2>(a), "pathA"); 511 } 512 { 513 auto ad = std::find_if( 514 associationData.begin(), associationData.end(), 515 [](const auto& ad) { return std::get<0>(ad) == "ownerB"; }); 516 ASSERT_NE(ad, associationData.end()); 517 518 auto& a = std::get<1>(*ad); 519 ASSERT_EQ(std::get<0>(a), "type1"); 520 ASSERT_EQ(std::get<1>(a), "typeB"); 521 ASSERT_EQ(std::get<2>(a), "pathX"); 522 } 523 } 524 525 TEST_F(TestAssociations, moveAssocToPendingNoOp) 526 { 527 AssociationMaps assocMaps; 528 529 // Not an association, so it shouldn't do anything 530 moveAssociationToPending(io, defaultEndpoint, assocMaps, *server); 531 532 EXPECT_TRUE(assocMaps.pending.empty()); 533 EXPECT_TRUE(assocMaps.owners.empty()); 534 EXPECT_TRUE(assocMaps.ifaces.empty()); 535 } 536 537 TEST_F(TestAssociations, moveAssocToPending) 538 { 539 AssociationMaps assocMaps; 540 assocMaps.owners = createDefaultOwnerAssociation(); 541 assocMaps.ifaces = createDefaultInterfaceAssociation(server); 542 543 moveAssociationToPending(io, defaultEndpoint, assocMaps, *server); 544 545 // Check it's now pending 546 EXPECT_EQ(assocMaps.pending.size(), 1); 547 EXPECT_EQ(assocMaps.pending.begin()->first, defaultEndpoint); 548 549 // No more assoc owners 550 EXPECT_TRUE(assocMaps.owners.empty()); 551 552 // Check the association interfaces were removed 553 { 554 auto assocs = assocMaps.ifaces.find(defaultFwdPath); 555 auto& iface = std::get<ifacePos>(assocs->second); 556 auto& endpoints = std::get<endpointsPos>(assocs->second); 557 558 EXPECT_EQ(iface.get(), nullptr); 559 EXPECT_TRUE(endpoints.empty()); 560 } 561 { 562 auto assocs = assocMaps.ifaces.find(defaultRevPath); 563 auto& iface = std::get<ifacePos>(assocs->second); 564 auto& endpoints = std::get<endpointsPos>(assocs->second); 565 566 EXPECT_EQ(iface.get(), nullptr); 567 EXPECT_TRUE(endpoints.empty()); 568 } 569 } 570