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