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