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