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": "no_vpd_for_fru"
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, "no_vpd_for_fru");
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": "no_vpd_for_fru",
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, "no_vpd_for_fru");
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