1 /** 2 * Copyright © 2019 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #include "extensions/openpower-pels/registry.hpp" 17 18 #include <filesystem> 19 #include <fstream> 20 #include <nlohmann/json.hpp> 21 22 #include <gtest/gtest.h> 23 24 using namespace openpower::pels::message; 25 using namespace openpower::pels; 26 namespace fs = std::filesystem; 27 28 const auto registryData = R"( 29 { 30 "PELs": 31 [ 32 { 33 "Name": "xyz.openbmc_project.Power.Fault", 34 "Subsystem": "power_supply", 35 36 "SRC": 37 { 38 "ReasonCode": "0x2030" 39 }, 40 41 "Documentation": 42 { 43 "Description": "A PGOOD Fault", 44 "Message": "PS had a PGOOD Fault" 45 } 46 }, 47 48 { 49 "Name": "xyz.openbmc_project.Power.OverVoltage", 50 "Subsystem": "power_control_hw", 51 "Severity": 52 [ 53 { 54 "System": "systemA", 55 "SevValue": "unrecoverable" 56 }, 57 { 58 "System": "systemB", 59 "SevValue": "recovered" 60 }, 61 { 62 "SevValue": "predictive" 63 } 64 ], 65 "MfgSeverity": "non_error", 66 "ActionFlags": ["service_action", "report", "call_home"], 67 "MfgActionFlags": ["hidden"], 68 69 "SRC": 70 { 71 "ReasonCode": "0x2333", 72 "Type": "BD", 73 "SymptomIDFields": ["SRCWord5", "SRCWord6", "SRCWord7"], 74 "Words6To9": 75 { 76 "6": 77 { 78 "Description": "Failing unit number", 79 "AdditionalDataPropSource": "PS_NUM" 80 }, 81 82 "7": 83 { 84 "Description": "bad voltage", 85 "AdditionalDataPropSource": "VOLTAGE" 86 } 87 } 88 }, 89 90 "Documentation": 91 { 92 "Description": "A PGOOD Fault", 93 "Message": "PS %1 had a PGOOD Fault", 94 "MessageArgSources": 95 [ 96 "SRCWord6" 97 ], 98 "Notes": [ 99 "In the UserData section there is a JSON", 100 "dump that provides debug information." 101 ] 102 } 103 }, 104 105 { 106 "Name": "xyz.openbmc_project.Common.Error.Timeout", 107 "PossibleSubsystems": ["processor", "memory"], 108 109 "SRC": 110 { 111 "ReasonCode": "0x2030" 112 }, 113 "Documentation": 114 { 115 "Description": "A PGOOD Fault", 116 "Message": "PS had a PGOOD Fault" 117 } 118 } 119 ] 120 } 121 )"; 122 123 class RegistryTest : public ::testing::Test 124 { 125 protected: 126 static void SetUpTestCase() 127 { 128 char path[] = "/tmp/regtestXXXXXX"; 129 regDir = mkdtemp(path); 130 } 131 132 static void TearDownTestCase() 133 { 134 fs::remove_all(regDir); 135 } 136 137 static std::string writeData(const char* data) 138 { 139 fs::path path = regDir / "registry.json"; 140 std::ofstream stream{path}; 141 stream << data; 142 return path; 143 } 144 145 static fs::path regDir; 146 }; 147 148 fs::path RegistryTest::regDir{}; 149 150 TEST_F(RegistryTest, TestNoEntry) 151 { 152 auto path = RegistryTest::writeData(registryData); 153 Registry registry{path}; 154 155 auto entry = registry.lookup("foo", LookupType::name); 156 EXPECT_FALSE(entry); 157 } 158 159 TEST_F(RegistryTest, TestFindEntry) 160 { 161 auto path = RegistryTest::writeData(registryData); 162 Registry registry{path}; 163 164 auto entry = registry.lookup("xyz.openbmc_project.Power.OverVoltage", 165 LookupType::name); 166 ASSERT_TRUE(entry); 167 EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.OverVoltage"); 168 EXPECT_EQ(entry->subsystem, 0x62); 169 170 ASSERT_EQ(entry->severity->size(), 3); 171 EXPECT_EQ((*entry->severity)[0].severity, 0x40); 172 EXPECT_EQ((*entry->severity)[0].system, "systemA"); 173 EXPECT_EQ((*entry->severity)[1].severity, 0x10); 174 EXPECT_EQ((*entry->severity)[1].system, "systemB"); 175 EXPECT_EQ((*entry->severity)[2].severity, 0x20); 176 EXPECT_EQ((*entry->severity)[2].system, ""); 177 178 EXPECT_EQ(entry->mfgSeverity->size(), 1); 179 EXPECT_EQ((*entry->mfgSeverity)[0].severity, 0x00); 180 181 EXPECT_EQ(*(entry->actionFlags), 0xA800); 182 EXPECT_EQ(*(entry->mfgActionFlags), 0x4000); 183 EXPECT_EQ(entry->componentID, 0x2300); 184 EXPECT_FALSE(entry->eventType); 185 EXPECT_FALSE(entry->eventScope); 186 187 EXPECT_EQ(entry->src.type, 0xBD); 188 EXPECT_EQ(entry->src.reasonCode, 0x2333); 189 190 auto& hexwords = entry->src.hexwordADFields; 191 EXPECT_TRUE(hexwords); 192 EXPECT_EQ((*hexwords).size(), 2); 193 194 auto word = (*hexwords).find(6); 195 EXPECT_NE(word, (*hexwords).end()); 196 EXPECT_EQ(std::get<0>(word->second), "PS_NUM"); 197 198 word = (*hexwords).find(7); 199 EXPECT_NE(word, (*hexwords).end()); 200 EXPECT_EQ(std::get<0>(word->second), "VOLTAGE"); 201 202 auto& sid = entry->src.symptomID; 203 EXPECT_TRUE(sid); 204 EXPECT_EQ((*sid).size(), 3); 205 EXPECT_NE(std::find((*sid).begin(), (*sid).end(), 5), (*sid).end()); 206 EXPECT_NE(std::find((*sid).begin(), (*sid).end(), 6), (*sid).end()); 207 EXPECT_NE(std::find((*sid).begin(), (*sid).end(), 7), (*sid).end()); 208 209 EXPECT_EQ(entry->doc.description, "A PGOOD Fault"); 210 EXPECT_EQ(entry->doc.message, "PS %1 had a PGOOD Fault"); 211 auto& hexwordSource = entry->doc.messageArgSources; 212 EXPECT_TRUE(hexwordSource); 213 EXPECT_EQ((*hexwordSource).size(), 1); 214 EXPECT_EQ((*hexwordSource).front(), "SRCWord6"); 215 216 entry = registry.lookup("0x2333", LookupType::reasonCode); 217 ASSERT_TRUE(entry); 218 EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.OverVoltage"); 219 } 220 221 // Check the entry that mostly uses defaults 222 TEST_F(RegistryTest, TestFindEntryMinimal) 223 { 224 auto path = RegistryTest::writeData(registryData); 225 Registry registry{path}; 226 227 auto entry = 228 registry.lookup("xyz.openbmc_project.Power.Fault", LookupType::name); 229 ASSERT_TRUE(entry); 230 EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.Fault"); 231 EXPECT_EQ(entry->subsystem, 0x61); 232 EXPECT_FALSE(entry->severity); 233 EXPECT_FALSE(entry->mfgSeverity); 234 EXPECT_FALSE(entry->mfgActionFlags); 235 EXPECT_FALSE(entry->actionFlags); 236 EXPECT_EQ(entry->componentID, 0x2000); 237 EXPECT_FALSE(entry->eventType); 238 EXPECT_FALSE(entry->eventScope); 239 240 EXPECT_EQ(entry->src.reasonCode, 0x2030); 241 EXPECT_EQ(entry->src.type, 0xBD); 242 EXPECT_FALSE(entry->src.hexwordADFields); 243 EXPECT_FALSE(entry->src.symptomID); 244 } 245 246 TEST_F(RegistryTest, TestBadJSON) 247 { 248 auto path = RegistryTest::writeData("bad {} json"); 249 250 Registry registry{path}; 251 252 EXPECT_FALSE(registry.lookup("foo", LookupType::name)); 253 } 254 255 // Test the helper functions the use the pel_values data. 256 TEST_F(RegistryTest, TestHelperFunctions) 257 { 258 using namespace openpower::pels::message::helper; 259 EXPECT_EQ(getSubsystem("input_power_source"), 0xA1); 260 EXPECT_THROW(getSubsystem("foo"), std::runtime_error); 261 262 EXPECT_EQ(getSeverity("symptom_recovered"), 0x71); 263 EXPECT_THROW(getSeverity("foo"), std::runtime_error); 264 265 EXPECT_EQ(getEventType("dump_notification"), 0x08); 266 EXPECT_THROW(getEventType("foo"), std::runtime_error); 267 268 EXPECT_EQ(getEventScope("possibly_multiple_platforms"), 0x04); 269 EXPECT_THROW(getEventScope("foo"), std::runtime_error); 270 271 std::vector<std::string> flags{"service_action", "dont_report", 272 "termination"}; 273 EXPECT_EQ(getActionFlags(flags), 0x9100); 274 275 flags.clear(); 276 flags.push_back("foo"); 277 EXPECT_THROW(getActionFlags(flags), std::runtime_error); 278 } 279 280 TEST_F(RegistryTest, TestGetSRCReasonCode) 281 { 282 using namespace openpower::pels::message::helper; 283 EXPECT_EQ(getSRCReasonCode(R"({"ReasonCode": "0x5555"})"_json, "foo"), 284 0x5555); 285 286 EXPECT_THROW(getSRCReasonCode(R"({"ReasonCode": "ZZZZ"})"_json, "foo"), 287 std::runtime_error); 288 } 289 290 TEST_F(RegistryTest, TestGetSRCType) 291 { 292 using namespace openpower::pels::message::helper; 293 EXPECT_EQ(getSRCType(R"({"Type": "11"})"_json, "foo"), 0x11); 294 EXPECT_EQ(getSRCType(R"({"Type": "BF"})"_json, "foo"), 0xBF); 295 296 EXPECT_THROW(getSRCType(R"({"Type": "1"})"_json, "foo"), 297 std::runtime_error); 298 299 EXPECT_THROW(getSRCType(R"({"Type": "111"})"_json, "foo"), 300 std::runtime_error); 301 } 302 303 TEST_F(RegistryTest, TestGetSRCHexwordFields) 304 { 305 using namespace openpower::pels::message::helper; 306 const auto hexwords = R"( 307 {"Words6To9": 308 { 309 "8": 310 { 311 "Description": "TEST", 312 "AdditionalDataPropSource": "TEST" 313 } 314 } 315 })"_json; 316 317 auto fields = getSRCHexwordFields(hexwords, "foo"); 318 EXPECT_TRUE(fields); 319 auto word = fields->find(8); 320 EXPECT_NE(word, fields->end()); 321 322 const auto theInvalidRWord = R"( 323 {"Words6To9": 324 { 325 "R": 326 { 327 "Description": "TEST", 328 "AdditionalDataPropSource": "TEST" 329 } 330 } 331 })"_json; 332 333 EXPECT_THROW(getSRCHexwordFields(theInvalidRWord, "foo"), 334 std::runtime_error); 335 } 336 337 TEST_F(RegistryTest, TestGetSRCSymptomIDFields) 338 { 339 using namespace openpower::pels::message::helper; 340 const auto sID = R"( 341 { 342 "SymptomIDFields": ["SRCWord3", "SRCWord4", "SRCWord5"] 343 })"_json; 344 345 auto fields = getSRCSymptomIDFields(sID, "foo"); 346 EXPECT_NE(std::find(fields->begin(), fields->end(), 3), fields->end()); 347 EXPECT_NE(std::find(fields->begin(), fields->end(), 4), fields->end()); 348 EXPECT_NE(std::find(fields->begin(), fields->end(), 5), fields->end()); 349 350 const auto badField = R"( 351 { 352 "SymptomIDFields": ["SRCWord3", "SRCWord4", "SRCWord"] 353 })"_json; 354 355 EXPECT_THROW(getSRCSymptomIDFields(badField, "foo"), std::runtime_error); 356 } 357 358 TEST_F(RegistryTest, TestGetComponentID) 359 { 360 using namespace openpower::pels::message::helper; 361 362 // Get it from the JSON 363 auto id = 364 getComponentID(0xBD, 0x4200, R"({"ComponentID":"0x4200"})"_json, "foo"); 365 EXPECT_EQ(id, 0x4200); 366 367 // Get it from the reason code on a 0xBD SRC 368 id = getComponentID(0xBD, 0x6700, R"({})"_json, "foo"); 369 EXPECT_EQ(id, 0x6700); 370 371 // Not present on a 0x11 SRC 372 EXPECT_THROW(getComponentID(0x11, 0x8800, R"({})"_json, "foo"), 373 std::runtime_error); 374 } 375 376 // Test when callouts are in the JSON. 377 TEST_F(RegistryTest, TestGetCallouts) 378 { 379 std::vector<std::string> names; 380 381 { 382 // Callouts without AD, that depend on system type, 383 // where there is a default entry without a system type. 384 auto json = R"( 385 [ 386 { 387 "System": "system1", 388 "CalloutList": 389 [ 390 { 391 "Priority": "high", 392 "LocCode": "P1-C1" 393 }, 394 { 395 "Priority": "low", 396 "LocCode": "P1" 397 }, 398 { 399 "Priority": "low", 400 "SymbolicFRU": "service_docs" 401 }, 402 { 403 "Priority": "low", 404 "SymbolicFRUTrusted": "air_mover", 405 "UseInventoryLocCode": true 406 } 407 ] 408 }, 409 { 410 "CalloutList": 411 [ 412 { 413 "Priority": "medium", 414 "Procedure": "bmc_code" 415 }, 416 { 417 "Priority": "low", 418 "LocCode": "P3-C8", 419 "SymbolicFRUTrusted": "service_docs" 420 } 421 ] 422 423 } 424 ])"_json; 425 426 AdditionalData ad; 427 names.push_back("system1"); 428 429 auto callouts = Registry::getCallouts(json, names, ad); 430 EXPECT_EQ(callouts.size(), 4); 431 EXPECT_EQ(callouts[0].priority, "high"); 432 EXPECT_EQ(callouts[0].locCode, "P1-C1"); 433 EXPECT_EQ(callouts[0].procedure, ""); 434 EXPECT_EQ(callouts[0].symbolicFRU, ""); 435 EXPECT_EQ(callouts[0].symbolicFRUTrusted, ""); 436 EXPECT_EQ(callouts[1].priority, "low"); 437 EXPECT_EQ(callouts[1].locCode, "P1"); 438 EXPECT_EQ(callouts[1].procedure, ""); 439 EXPECT_EQ(callouts[1].symbolicFRU, ""); 440 EXPECT_EQ(callouts[1].symbolicFRUTrusted, ""); 441 EXPECT_EQ(callouts[2].priority, "low"); 442 EXPECT_EQ(callouts[2].locCode, ""); 443 EXPECT_EQ(callouts[2].procedure, ""); 444 EXPECT_EQ(callouts[2].symbolicFRU, "service_docs"); 445 EXPECT_EQ(callouts[2].symbolicFRUTrusted, ""); 446 EXPECT_EQ(callouts[3].priority, "low"); 447 EXPECT_EQ(callouts[3].locCode, ""); 448 EXPECT_EQ(callouts[3].procedure, ""); 449 EXPECT_EQ(callouts[3].symbolicFRU, ""); 450 EXPECT_EQ(callouts[3].symbolicFRUTrusted, "air_mover"); 451 EXPECT_EQ(callouts[3].useInventoryLocCode, true); 452 453 // system2 isn't in the JSON, so it will pick the default one 454 names[0] = "system2"; 455 callouts = Registry::getCallouts(json, names, ad); 456 EXPECT_EQ(callouts.size(), 2); 457 EXPECT_EQ(callouts[0].priority, "medium"); 458 EXPECT_EQ(callouts[0].locCode, ""); 459 EXPECT_EQ(callouts[0].procedure, "bmc_code"); 460 EXPECT_EQ(callouts[0].symbolicFRU, ""); 461 EXPECT_EQ(callouts[1].priority, "low"); 462 EXPECT_EQ(callouts[1].locCode, "P3-C8"); 463 EXPECT_EQ(callouts[1].procedure, ""); 464 EXPECT_EQ(callouts[1].symbolicFRU, ""); 465 EXPECT_EQ(callouts[1].symbolicFRUTrusted, "service_docs"); 466 EXPECT_EQ(callouts[1].useInventoryLocCode, false); 467 } 468 469 // Empty JSON array (treated as an error) 470 { 471 auto json = R"([])"_json; 472 AdditionalData ad; 473 names[0] = "system1"; 474 EXPECT_THROW(Registry::getCallouts(json, names, ad), 475 std::runtime_error); 476 } 477 478 { 479 // Callouts without AD, that depend on system type, 480 // where there isn't a default entry without a system type. 481 auto json = R"( 482 [ 483 { 484 "System": "system1", 485 "CalloutList": 486 [ 487 { 488 "Priority": "high", 489 "LocCode": "P1-C1" 490 }, 491 { 492 "Priority": "low", 493 "LocCode": "P1", 494 "SymbolicFRU": "1234567" 495 } 496 ] 497 }, 498 { 499 "System": "system2", 500 "CalloutList": 501 [ 502 { 503 "Priority": "medium", 504 "LocCode": "P7", 505 "CalloutType": "tool_fru" 506 } 507 ] 508 509 } 510 ])"_json; 511 512 AdditionalData ad; 513 names[0] = "system1"; 514 515 auto callouts = Registry::getCallouts(json, names, ad); 516 EXPECT_EQ(callouts.size(), 2); 517 EXPECT_EQ(callouts[0].priority, "high"); 518 EXPECT_EQ(callouts[0].locCode, "P1-C1"); 519 EXPECT_EQ(callouts[0].procedure, ""); 520 EXPECT_EQ(callouts[0].symbolicFRU, ""); 521 EXPECT_EQ(callouts[0].symbolicFRUTrusted, ""); 522 EXPECT_EQ(callouts[1].priority, "low"); 523 EXPECT_EQ(callouts[1].locCode, "P1"); 524 EXPECT_EQ(callouts[1].procedure, ""); 525 EXPECT_EQ(callouts[1].symbolicFRU, "1234567"); 526 EXPECT_EQ(callouts[1].symbolicFRUTrusted, ""); 527 528 names[0] = "system2"; 529 callouts = Registry::getCallouts(json, names, ad); 530 EXPECT_EQ(callouts.size(), 1); 531 EXPECT_EQ(callouts[0].priority, "medium"); 532 EXPECT_EQ(callouts[0].locCode, "P7"); 533 EXPECT_EQ(callouts[0].procedure, ""); 534 EXPECT_EQ(callouts[0].symbolicFRU, ""); 535 EXPECT_EQ(callouts[0].symbolicFRUTrusted, ""); 536 537 // There is no entry for system3 or a default system, 538 // so this should fail. 539 names[0] = "system3"; 540 EXPECT_THROW(Registry::getCallouts(json, names, ad), 541 std::runtime_error); 542 } 543 544 { 545 // Callouts that use the AdditionalData key PROC_NUM 546 // as an index into them, along with a system type. 547 // It supports PROC_NUMs 0 and 1. 548 auto json = R"( 549 { 550 "ADName": "PROC_NUM", 551 "CalloutsWithTheirADValues": 552 [ 553 { 554 "ADValue": "0", 555 "Callouts": 556 [ 557 { 558 "System": "system3", 559 "CalloutList": 560 [ 561 { 562 "Priority": "high", 563 "LocCode": "P1-C5" 564 }, 565 { 566 "Priority": "medium", 567 "LocCode": "P1-C6", 568 "SymbolicFRU": "1234567" 569 }, 570 { 571 "Priority": "low", 572 "Procedure": "bmc_code", 573 "CalloutType": "config_procedure" 574 } 575 ] 576 }, 577 { 578 "CalloutList": 579 [ 580 { 581 "Priority": "low", 582 "LocCode": "P55" 583 } 584 ] 585 } 586 ] 587 }, 588 { 589 "ADValue": "1", 590 "Callouts": 591 [ 592 { 593 "CalloutList": 594 [ 595 { 596 "Priority": "high", 597 "LocCode": "P1-C6", 598 "CalloutType": "external_fru" 599 } 600 ] 601 } 602 ] 603 } 604 ] 605 })"_json; 606 607 { 608 // Find callouts for PROC_NUM 0 on system3 609 std::vector<std::string> adData{"PROC_NUM=0"}; 610 AdditionalData ad{adData}; 611 names[0] = "system3"; 612 613 auto callouts = Registry::getCallouts(json, names, ad); 614 EXPECT_EQ(callouts.size(), 3); 615 EXPECT_EQ(callouts[0].priority, "high"); 616 EXPECT_EQ(callouts[0].locCode, "P1-C5"); 617 EXPECT_EQ(callouts[0].procedure, ""); 618 EXPECT_EQ(callouts[0].symbolicFRU, ""); 619 EXPECT_EQ(callouts[0].symbolicFRUTrusted, ""); 620 EXPECT_EQ(callouts[1].priority, "medium"); 621 EXPECT_EQ(callouts[1].locCode, "P1-C6"); 622 EXPECT_EQ(callouts[1].procedure, ""); 623 EXPECT_EQ(callouts[1].symbolicFRU, "1234567"); 624 EXPECT_EQ(callouts[1].symbolicFRUTrusted, ""); 625 EXPECT_EQ(callouts[2].priority, "low"); 626 EXPECT_EQ(callouts[2].locCode, ""); 627 EXPECT_EQ(callouts[2].procedure, "bmc_code"); 628 EXPECT_EQ(callouts[2].symbolicFRU, ""); 629 EXPECT_EQ(callouts[2].symbolicFRUTrusted, ""); 630 631 // Find callouts for PROC_NUM 0 that uses the default system entry. 632 names[0] = "system99"; 633 634 callouts = Registry::getCallouts(json, names, ad); 635 EXPECT_EQ(callouts.size(), 1); 636 EXPECT_EQ(callouts[0].priority, "low"); 637 EXPECT_EQ(callouts[0].locCode, "P55"); 638 EXPECT_EQ(callouts[0].procedure, ""); 639 EXPECT_EQ(callouts[0].symbolicFRU, ""); 640 EXPECT_EQ(callouts[0].symbolicFRUTrusted, ""); 641 } 642 { 643 // Find callouts for PROC_NUM 1 that uses a default system entry. 644 std::vector<std::string> adData{"PROC_NUM=1"}; 645 AdditionalData ad{adData}; 646 names[0] = "system1"; 647 648 auto callouts = Registry::getCallouts(json, names, ad); 649 EXPECT_EQ(callouts.size(), 1); 650 EXPECT_EQ(callouts[0].priority, "high"); 651 EXPECT_EQ(callouts[0].locCode, "P1-C6"); 652 EXPECT_EQ(callouts[0].procedure, ""); 653 EXPECT_EQ(callouts[0].symbolicFRU, ""); 654 EXPECT_EQ(callouts[0].symbolicFRUTrusted, ""); 655 } 656 { 657 // There is no entry for PROC_NUM 2, so no callouts 658 std::vector<std::string> adData{"PROC_NUM=2"}; 659 AdditionalData ad{adData}; 660 661 auto callouts = Registry::getCallouts(json, names, ad); 662 EXPECT_TRUE(callouts.empty()); 663 } 664 } 665 } 666 667 TEST_F(RegistryTest, TestNoSubsystem) 668 { 669 auto path = RegistryTest::writeData(registryData); 670 Registry registry{path}; 671 672 auto entry = registry.lookup("xyz.openbmc_project.Common.Error.Timeout", 673 LookupType::name); 674 ASSERT_TRUE(entry); 675 EXPECT_FALSE(entry->subsystem); 676 } 677