1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2019 IBM Corporation
3
4 #include "extensions/openpower-pels/src.hpp"
5 #include "mocks.hpp"
6 #include "pel_utils.hpp"
7
8 #include <fstream>
9
10 #include <gtest/gtest.h>
11
12 using namespace openpower::pels;
13 using ::testing::_;
14 using ::testing::DoAll;
15 using ::testing::InvokeWithoutArgs;
16 using ::testing::NiceMock;
17 using ::testing::Return;
18 using ::testing::SetArgReferee;
19 using ::testing::Throw;
20 namespace fs = std::filesystem;
21
22 const auto testRegistry = R"(
23 {
24 "PELs":
25 [
26 {
27 "Name": "xyz.openbmc_project.Error.Test",
28 "Subsystem": "bmc_firmware",
29 "SRC":
30 {
31 "ReasonCode": "0xABCD",
32 "Words6To9":
33 {
34 "6":
35 {
36 "Description": "Component ID",
37 "AdditionalDataPropSource": "COMPID"
38 },
39 "7":
40 {
41 "Description": "Failure count",
42 "AdditionalDataPropSource": "FREQUENCY"
43 },
44 "8":
45 {
46 "Description": "Time period",
47 "AdditionalDataPropSource": "DURATION"
48 },
49 "9":
50 {
51 "Description": "Error code",
52 "AdditionalDataPropSource": "ERRORCODE"
53 }
54 }
55 },
56 "Documentation":
57 {
58 "Description": "A Component Fault",
59 "Message": "Comp %1 failed %2 times over %3 secs with ErrorCode %4",
60 "MessageArgSources":
61 [
62 "SRCWord6", "SRCWord7", "SRCWord8", "SRCWord9"
63 ]
64 }
65 }
66 ]
67 }
68 )";
69
70 class SRCTest : public ::testing::Test
71 {
72 protected:
SetUpTestCase()73 static void SetUpTestCase()
74 {
75 char path[] = "/tmp/srctestXXXXXX";
76 regDir = mkdtemp(path);
77 }
78
TearDownTestCase()79 static void TearDownTestCase()
80 {
81 fs::remove_all(regDir);
82 }
83
writeData(const char * data)84 static std::string writeData(const char* data)
85 {
86 fs::path path = regDir / "registry.json";
87 std::ofstream stream{path};
88 stream << data;
89 return path;
90 }
91
92 static fs::path regDir;
93 };
94
95 fs::path SRCTest::regDir{};
96
TEST_F(SRCTest,UnflattenFlattenTestNoCallouts)97 TEST_F(SRCTest, UnflattenFlattenTestNoCallouts)
98 {
99 auto data = pelDataFactory(TestPELType::primarySRCSection);
100
101 Stream stream{data};
102 SRC src{stream};
103
104 EXPECT_TRUE(src.valid());
105
106 EXPECT_EQ(src.header().id, 0x5053);
107 EXPECT_EQ(src.header().size, 0x50);
108 EXPECT_EQ(src.header().version, 0x01);
109 EXPECT_EQ(src.header().subType, 0x01);
110 EXPECT_EQ(src.header().componentID, 0x0202);
111
112 EXPECT_EQ(src.version(), 0x02);
113 EXPECT_EQ(src.flags(), 0x00);
114 EXPECT_EQ(src.hexWordCount(), 9);
115 EXPECT_EQ(src.size(), 0x48);
116
117 const auto& hexwords = src.hexwordData();
118 EXPECT_EQ(0x02020255, hexwords[0]);
119 EXPECT_EQ(0x03030310, hexwords[1]);
120 EXPECT_EQ(0x04040404, hexwords[2]);
121 EXPECT_EQ(0x05050505, hexwords[3]);
122 EXPECT_EQ(0x06060606, hexwords[4]);
123 EXPECT_EQ(0x07070707, hexwords[5]);
124 EXPECT_EQ(0x08080808, hexwords[6]);
125 EXPECT_EQ(0x09090909, hexwords[7]);
126
127 EXPECT_EQ(src.asciiString(), "BD8D5678 ");
128 EXPECT_FALSE(src.callouts());
129
130 // Flatten
131 std::vector<uint8_t> newData;
132 Stream newStream{newData};
133
134 src.flatten(newStream);
135 EXPECT_EQ(data, newData);
136 }
137
TEST_F(SRCTest,UnflattenFlattenTest2Callouts)138 TEST_F(SRCTest, UnflattenFlattenTest2Callouts)
139 {
140 auto data = pelDataFactory(TestPELType::primarySRCSection2Callouts);
141
142 Stream stream{data};
143 SRC src{stream};
144
145 EXPECT_TRUE(src.valid());
146 EXPECT_EQ(src.flags(), 0x01); // Additional sections within the SRC.
147
148 // Spot check the SRC fields, but they're the same as above
149 EXPECT_EQ(src.asciiString(), "BD8D5678 ");
150
151 // There should be 2 callouts
152 const auto& calloutsSection = src.callouts();
153 ASSERT_TRUE(calloutsSection);
154 const auto& callouts = calloutsSection->callouts();
155 EXPECT_EQ(callouts.size(), 2);
156
157 // spot check that each callout has the right substructures
158 EXPECT_TRUE(callouts.front()->fruIdentity());
159 EXPECT_FALSE(callouts.front()->pceIdentity());
160 EXPECT_FALSE(callouts.front()->mru());
161
162 EXPECT_TRUE(callouts.back()->fruIdentity());
163 EXPECT_TRUE(callouts.back()->pceIdentity());
164 EXPECT_TRUE(callouts.back()->mru());
165
166 // Flatten
167 std::vector<uint8_t> newData;
168 Stream newStream{newData};
169
170 src.flatten(newStream);
171 EXPECT_EQ(data, newData);
172 }
173
174 // Create an SRC from the message registry
TEST_F(SRCTest,CreateTestNoCallouts)175 TEST_F(SRCTest, CreateTestNoCallouts)
176 {
177 message::Entry entry;
178 entry.src.type = 0xBD;
179 entry.src.reasonCode = 0xABCD;
180 entry.subsystem = 0x42;
181 entry.src.hexwordADFields = {
182 {5, {"TEST1", "DESCR1"}}, // Not a user defined word
183 {6, {"TEST1", "DESCR1"}},
184 {7, {"TEST2", "DESCR2"}},
185 {8, {"TEST3", "DESCR3"}},
186 {9, {"TEST4", "DESCR4"}}};
187
188 // Values for the SRC words pointed to above
189 std::map<std::string, std::string> adData{
190 {"TEST1", "0x12345678"},
191 {"TEST2", "12345678"},
192 {"TEST3", "0XDEF"},
193 {"TEST4", "Z"}};
194 AdditionalData ad{adData};
195 NiceMock<MockDataInterface> dataIface;
196
197 EXPECT_CALL(dataIface, getMotherboardCCIN).WillOnce(Return("ABCD"));
198
199 SRC src{entry, ad, dataIface};
200
201 EXPECT_TRUE(src.valid());
202 EXPECT_FALSE(src.isPowerFaultEvent());
203 EXPECT_EQ(src.size(), baseSRCSize);
204
205 const auto& hexwords = src.hexwordData();
206
207 // The spec always refers to SRC words 2 - 9, and as the hexwordData()
208 // array index starts at 0 use the math in the [] below to make it easier
209 // to tell what is being accessed.
210 EXPECT_EQ(hexwords[2 - 2] & 0xF0000000, 0); // Partition dump status
211 EXPECT_EQ(hexwords[2 - 2] & 0x00F00000, 0); // Partition boot type
212 EXPECT_EQ(hexwords[2 - 2] & 0x000000FF, 0x55); // SRC format
213 EXPECT_EQ(hexwords[3 - 2] & 0x000000FF, 0x10); // BMC position
214 EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0xABCD0000); // Motherboard CCIN
215
216 // Validate more fields here as the code starts filling them in.
217
218 // Ensure hex word 5 wasn't allowed to be set to TEST1's contents
219 // And that none of the error status flags are set
220 EXPECT_EQ(hexwords[5 - 2], 0);
221
222 // The user defined hex word fields specifed in the additional data.
223 EXPECT_EQ(hexwords[6 - 2], 0x12345678); // TEST1
224 EXPECT_EQ(hexwords[7 - 2], 12345678); // TEST2
225 EXPECT_EQ(hexwords[8 - 2], 0xdef); // TEST3
226 EXPECT_EQ(hexwords[9 - 2], 0); // TEST4, but can't convert a 'Z'
227
228 EXPECT_EQ(src.asciiString(), "BD42ABCD ");
229
230 // No callouts
231 EXPECT_FALSE(src.callouts());
232
233 // May as well spot check the flatten/unflatten
234 std::vector<uint8_t> data;
235 Stream stream{data};
236 src.flatten(stream);
237
238 stream.offset(0);
239 SRC newSRC{stream};
240
241 EXPECT_TRUE(newSRC.valid());
242 EXPECT_EQ(newSRC.asciiString(), src.asciiString());
243 EXPECT_FALSE(newSRC.callouts());
244 }
245
246 // Test when the CCIN string isn't a 4 character number
TEST_F(SRCTest,BadCCINTest)247 TEST_F(SRCTest, BadCCINTest)
248 {
249 message::Entry entry;
250 entry.src.type = 0xBD;
251 entry.src.reasonCode = 0xABCD;
252 entry.subsystem = 0x42;
253
254 std::map<std::string, std::string> adData{};
255 AdditionalData ad{adData};
256 NiceMock<MockDataInterface> dataIface;
257
258 // First it isn't a number, then it is too long,
259 // then it is empty.
260 EXPECT_CALL(dataIface, getMotherboardCCIN)
261 .WillOnce(Return("X"))
262 .WillOnce(Return("12345"))
263 .WillOnce(Return(""));
264
265 // The CCIN in the first half should still be 0 each time.
266 {
267 SRC src{entry, ad, dataIface};
268 EXPECT_TRUE(src.valid());
269 const auto& hexwords = src.hexwordData();
270 EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
271 }
272
273 {
274 SRC src{entry, ad, dataIface};
275 EXPECT_TRUE(src.valid());
276 const auto& hexwords = src.hexwordData();
277 EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
278 }
279
280 {
281 SRC src{entry, ad, dataIface};
282 EXPECT_TRUE(src.valid());
283 const auto& hexwords = src.hexwordData();
284 EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
285 }
286 }
287
288 // Test the getErrorDetails function
TEST_F(SRCTest,MessageSubstitutionTest)289 TEST_F(SRCTest, MessageSubstitutionTest)
290 {
291 auto path = SRCTest::writeData(testRegistry);
292 message::Registry registry{path};
293 auto entry = registry.lookup("0xABCD", message::LookupType::reasonCode);
294
295 std::map<std::string, std::string> adData{
296 {"COMPID", "0x1"},
297 {"FREQUENCY", "0x4"},
298 {"DURATION", "30"},
299 {"ERRORCODE", "0x01ABCDEF"}};
300 AdditionalData ad{adData};
301 NiceMock<MockDataInterface> dataIface;
302
303 SRC src{*entry, ad, dataIface};
304 EXPECT_TRUE(src.valid());
305
306 auto errorDetails = src.getErrorDetails(registry, DetailLevel::message);
307 ASSERT_TRUE(errorDetails);
308 EXPECT_EQ(errorDetails.value(),
309 "Comp 0x00000001 failed 0x00000004 times over 0x0000001E secs "
310 "with ErrorCode 0x01ABCDEF");
311 }
312 // Test that an inventory path callout string is
313 // converted into the appropriate FRU callout.
TEST_F(SRCTest,InventoryCalloutTest)314 TEST_F(SRCTest, InventoryCalloutTest)
315 {
316 message::Entry entry;
317 entry.src.type = 0xBD;
318 entry.src.reasonCode = 0xABCD;
319 entry.subsystem = 0x42;
320
321 std::map<std::string, std::string> adData{
322 {"CALLOUT_INVENTORY_PATH", "motherboard"}};
323 AdditionalData ad{adData};
324 NiceMock<MockDataInterface> dataIface;
325
326 EXPECT_CALL(dataIface, getLocationCode("motherboard"))
327 .WillOnce(Return("UTMS-P1"));
328
329 EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
330 .Times(1)
331 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
332 SetArgReferee<3>("123456789ABC")));
333
334 SRC src{entry, ad, dataIface};
335 EXPECT_TRUE(src.valid());
336
337 ASSERT_TRUE(src.callouts());
338
339 EXPECT_EQ(src.callouts()->callouts().size(), 1);
340
341 auto& callout = src.callouts()->callouts().front();
342
343 EXPECT_EQ(callout->locationCode(), "UTMS-P1");
344 EXPECT_EQ(callout->priority(), 'H');
345
346 auto& fru = callout->fruIdentity();
347
348 EXPECT_EQ(fru->getPN().value(), "1234567");
349 EXPECT_EQ(fru->getCCIN().value(), "CCCC");
350 EXPECT_EQ(fru->getSN().value(), "123456789ABC");
351
352 // flatten and unflatten
353 std::vector<uint8_t> data;
354 Stream stream{data};
355 src.flatten(stream);
356
357 stream.offset(0);
358 SRC newSRC{stream};
359 EXPECT_TRUE(newSRC.valid());
360 ASSERT_TRUE(src.callouts());
361 EXPECT_EQ(src.callouts()->callouts().size(), 1);
362 }
363
364 // Test that when the location code can't be obtained that
365 // no callout is added.
TEST_F(SRCTest,InventoryCalloutNoLocCodeTest)366 TEST_F(SRCTest, InventoryCalloutNoLocCodeTest)
367 {
368 message::Entry entry;
369 entry.src.type = 0xBD;
370 entry.src.reasonCode = 0xABCD;
371 entry.subsystem = 0x42;
372
373 std::map<std::string, std::string> adData{
374 {"CALLOUT_INVENTORY_PATH", "motherboard"}};
375 AdditionalData ad{adData};
376 NiceMock<MockDataInterface> dataIface;
377
378 auto func = []() {
379 throw sdbusplus::exception::SdBusError(5, "Error");
380 return std::string{};
381 };
382
383 EXPECT_CALL(dataIface, getLocationCode("motherboard"))
384 .Times(1)
385 .WillOnce(InvokeWithoutArgs(func));
386
387 EXPECT_CALL(dataIface, getHWCalloutFields(_, _, _, _)).Times(0);
388
389 SRC src{entry, ad, dataIface};
390 EXPECT_TRUE(src.valid());
391
392 ASSERT_FALSE(src.callouts());
393
394 // flatten and unflatten
395 std::vector<uint8_t> data;
396 Stream stream{data};
397 src.flatten(stream);
398
399 stream.offset(0);
400 SRC newSRC{stream};
401 EXPECT_TRUE(newSRC.valid());
402 ASSERT_FALSE(src.callouts());
403 }
404
405 // Test that when the VPD can't be obtained that
406 // a callout is still created.
TEST_F(SRCTest,InventoryCalloutNoVPDTest)407 TEST_F(SRCTest, InventoryCalloutNoVPDTest)
408 {
409 message::Entry entry;
410 entry.src.type = 0xBD;
411 entry.src.reasonCode = 0xABCD;
412 entry.subsystem = 0x42;
413
414 std::map<std::string, std::string> adData{
415 {"CALLOUT_INVENTORY_PATH", "motherboard"}};
416 AdditionalData ad{adData};
417 NiceMock<MockDataInterface> dataIface;
418
419 EXPECT_CALL(dataIface, getLocationCode("motherboard"))
420 .Times(1)
421 .WillOnce(Return("UTMS-P10"));
422
423 auto func = []() { throw sdbusplus::exception::SdBusError(5, "Error"); };
424
425 EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
426 .Times(1)
427 .WillOnce(InvokeWithoutArgs(func));
428
429 SRC src{entry, ad, dataIface};
430 EXPECT_TRUE(src.valid());
431 ASSERT_TRUE(src.callouts());
432 EXPECT_EQ(src.callouts()->callouts().size(), 1);
433
434 auto& callout = src.callouts()->callouts().front();
435 EXPECT_EQ(callout->locationCode(), "UTMS-P10");
436 EXPECT_EQ(callout->priority(), 'H');
437
438 auto& fru = callout->fruIdentity();
439
440 EXPECT_EQ(fru->getPN(), "");
441 EXPECT_EQ(fru->getCCIN(), "");
442 EXPECT_EQ(fru->getSN(), "");
443 EXPECT_FALSE(fru->getMaintProc());
444
445 // flatten and unflatten
446 std::vector<uint8_t> data;
447 Stream stream{data};
448 src.flatten(stream);
449
450 stream.offset(0);
451 SRC newSRC{stream};
452 EXPECT_TRUE(newSRC.valid());
453 ASSERT_TRUE(src.callouts());
454 EXPECT_EQ(src.callouts()->callouts().size(), 1);
455 }
456
TEST_F(SRCTest,RegistryCalloutTest)457 TEST_F(SRCTest, RegistryCalloutTest)
458 {
459 message::Entry entry;
460 entry.src.type = 0xBD;
461 entry.src.reasonCode = 0xABCD;
462 entry.src.deconfigFlag = true;
463 entry.src.checkstopFlag = true;
464 entry.subsystem = 0x42;
465
466 entry.callouts = R"(
467 [
468 {
469 "System": "systemA",
470 "CalloutList":
471 [
472 {
473 "Priority": "high",
474 "SymbolicFRU": "service_docs"
475 },
476 {
477 "Priority": "medium",
478 "Procedure": "BMC0001"
479 }
480 ]
481 },
482 {
483 "System": "systemB",
484 "CalloutList":
485 [
486 {
487 "Priority": "high",
488 "LocCode": "P0-C8",
489 "SymbolicFRUTrusted": "service_docs"
490 },
491 {
492 "Priority": "medium",
493 "SymbolicFRUTrusted": "service_docs"
494 }
495 ]
496 },
497 {
498 "System": "systemC",
499 "CalloutList":
500 [
501 {
502 "Priority": "high",
503 "LocCode": "P0-C8"
504 },
505 {
506 "Priority": "medium",
507 "LocCode": "P0-C9"
508 }
509 ]
510 }
511 ])"_json;
512
513 {
514 // Call out a symbolic FRU and a procedure
515 AdditionalData ad;
516 NiceMock<MockDataInterface> dataIface;
517 std::vector<std::string> names{"systemA"};
518
519 EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
520
521 SRC src{entry, ad, dataIface};
522
523 EXPECT_TRUE(
524 src.getErrorStatusFlag(SRC::ErrorStatusFlags::deconfigured));
525 EXPECT_TRUE(src.getErrorStatusFlag(SRC::ErrorStatusFlags::hwCheckstop));
526
527 const auto& hexwords = src.hexwordData();
528 auto mask = static_cast<uint32_t>(SRC::ErrorStatusFlags::deconfigured) |
529 static_cast<uint32_t>(SRC::ErrorStatusFlags::hwCheckstop);
530 EXPECT_EQ(hexwords[5 - 2] & mask, mask);
531
532 auto& callouts = src.callouts()->callouts();
533 ASSERT_EQ(callouts.size(), 2);
534
535 EXPECT_EQ(callouts[0]->locationCodeSize(), 0);
536 EXPECT_EQ(callouts[0]->priority(), 'H');
537
538 EXPECT_EQ(callouts[1]->locationCodeSize(), 0);
539 EXPECT_EQ(callouts[1]->priority(), 'M');
540
541 auto& fru1 = callouts[0]->fruIdentity();
542 EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
543 EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRU);
544 EXPECT_FALSE(fru1->getMaintProc());
545 EXPECT_FALSE(fru1->getSN());
546 EXPECT_FALSE(fru1->getCCIN());
547
548 auto& fru2 = callouts[1]->fruIdentity();
549 EXPECT_EQ(fru2->getMaintProc().value(), "BMC0001");
550 EXPECT_EQ(fru2->failingComponentType(),
551 src::FRUIdentity::maintenanceProc);
552 EXPECT_FALSE(fru2->getPN());
553 EXPECT_FALSE(fru2->getSN());
554 EXPECT_FALSE(fru2->getCCIN());
555 }
556
557 {
558 // Call out a trusted symbolic FRU with a location code, and
559 // another one without.
560 AdditionalData ad;
561 NiceMock<MockDataInterface> dataIface;
562 std::vector<std::string> names{"systemB"};
563
564 EXPECT_CALL(dataIface, expandLocationCode).WillOnce(Return("P0-C8"));
565 EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
566
567 SRC src{entry, ad, dataIface};
568
569 auto& callouts = src.callouts()->callouts();
570 EXPECT_EQ(callouts.size(), 2);
571
572 EXPECT_EQ(callouts[0]->locationCode(), "P0-C8");
573 EXPECT_EQ(callouts[0]->priority(), 'H');
574
575 EXPECT_EQ(callouts[1]->locationCodeSize(), 0);
576 EXPECT_EQ(callouts[1]->priority(), 'M');
577
578 auto& fru1 = callouts[0]->fruIdentity();
579 EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
580 EXPECT_EQ(fru1->failingComponentType(),
581 src::FRUIdentity::symbolicFRUTrustedLocCode);
582 EXPECT_FALSE(fru1->getMaintProc());
583 EXPECT_FALSE(fru1->getSN());
584 EXPECT_FALSE(fru1->getCCIN());
585
586 // It asked for a trusted symbolic FRU, but no location code
587 // was provided so it is switched back to a normal one
588 auto& fru2 = callouts[1]->fruIdentity();
589 EXPECT_EQ(fru2->getPN().value(), "SVCDOCS");
590 EXPECT_EQ(fru2->failingComponentType(), src::FRUIdentity::symbolicFRU);
591 EXPECT_FALSE(fru2->getMaintProc());
592 EXPECT_FALSE(fru2->getSN());
593 EXPECT_FALSE(fru2->getCCIN());
594 }
595
596 {
597 // Two hardware callouts
598 AdditionalData ad;
599 NiceMock<MockDataInterface> dataIface;
600 std::vector<std::string> names{"systemC"};
601
602 EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
603
604 EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
605 .WillOnce(Return("UXXX-P0-C8"));
606
607 EXPECT_CALL(dataIface, expandLocationCode("P0-C9", 0))
608 .WillOnce(Return("UXXX-P0-C9"));
609
610 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C8", 0, false))
611 .WillOnce(Return(std::vector<std::string>{
612 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"}));
613
614 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C9", 0, false))
615 .WillOnce(Return(std::vector<std::string>{
616 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu1"}));
617
618 EXPECT_CALL(
619 dataIface,
620 getHWCalloutFields(
621 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _,
622 _))
623 .Times(1)
624 .WillOnce(
625 DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
626 SetArgReferee<3>("123456789ABC")));
627
628 EXPECT_CALL(
629 dataIface,
630 getHWCalloutFields(
631 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu1", _, _,
632 _))
633 .Times(1)
634 .WillOnce(
635 DoAll(SetArgReferee<1>("2345678"), SetArgReferee<2>("DDDD"),
636 SetArgReferee<3>("23456789ABCD")));
637
638 SRC src{entry, ad, dataIface};
639
640 auto& callouts = src.callouts()->callouts();
641 EXPECT_EQ(callouts.size(), 2);
642
643 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C8");
644 EXPECT_EQ(callouts[0]->priority(), 'H');
645
646 auto& fru1 = callouts[0]->fruIdentity();
647 EXPECT_EQ(fru1->getPN().value(), "1234567");
648 EXPECT_EQ(fru1->getCCIN().value(), "CCCC");
649 EXPECT_EQ(fru1->getSN().value(), "123456789ABC");
650
651 EXPECT_EQ(callouts[1]->locationCode(), "UXXX-P0-C9");
652 EXPECT_EQ(callouts[1]->priority(), 'M');
653
654 auto& fru2 = callouts[1]->fruIdentity();
655 EXPECT_EQ(fru2->getPN().value(), "2345678");
656 EXPECT_EQ(fru2->getCCIN().value(), "DDDD");
657 EXPECT_EQ(fru2->getSN().value(), "23456789ABCD");
658 }
659 }
660
661 // Test that a symbolic FRU with a trusted location code callout
662 // from the registry can get its location from the
663 // CALLOUT_INVENTORY_PATH AdditionalData entry.
TEST_F(SRCTest,SymbolicFRUWithInvPathTest)664 TEST_F(SRCTest, SymbolicFRUWithInvPathTest)
665 {
666 message::Entry entry;
667 entry.src.type = 0xBD;
668 entry.src.reasonCode = 0xABCD;
669 entry.subsystem = 0x42;
670
671 entry.callouts = R"(
672 [{
673 "CalloutList":
674 [
675 {
676 "Priority": "high",
677 "SymbolicFRUTrusted": "service_docs",
678 "UseInventoryLocCode": true
679 },
680 {
681 "Priority": "medium",
682 "LocCode": "P0-C8",
683 "SymbolicFRUTrusted": "pwrsply"
684 }
685 ]
686 }])"_json;
687
688 {
689 // The location code for the first symbolic FRU callout will
690 // come from this inventory path since UseInventoryLocCode is set.
691 // In this case there will be no normal FRU callout for the motherboard.
692 std::map<std::string, std::string> adData{
693 {"CALLOUT_INVENTORY_PATH", "motherboard"}};
694 AdditionalData ad{adData};
695 NiceMock<MockDataInterface> dataIface;
696 std::vector<std::string> names{"systemA"};
697
698 EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
699
700 EXPECT_CALL(dataIface, getLocationCode("motherboard"))
701 .Times(1)
702 .WillOnce(Return("Ufcs-P10"));
703
704 EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
705 .WillOnce(Return("Ufcs-P0-C8"));
706
707 SRC src{entry, ad, dataIface};
708
709 auto& callouts = src.callouts()->callouts();
710 EXPECT_EQ(callouts.size(), 2);
711
712 // The location code for the first symbolic FRU callout with a
713 // trusted location code comes from the motherboard.
714 EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P10");
715 EXPECT_EQ(callouts[0]->priority(), 'H');
716 auto& fru1 = callouts[0]->fruIdentity();
717 EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
718 EXPECT_EQ(fru1->failingComponentType(),
719 src::FRUIdentity::symbolicFRUTrustedLocCode);
720
721 // The second trusted symbolic FRU callouts uses the location
722 // code in the registry as usual.
723 EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P0-C8");
724 EXPECT_EQ(callouts[1]->priority(), 'M');
725 auto& fru2 = callouts[1]->fruIdentity();
726 EXPECT_EQ(fru2->getPN().value(), "PWRSPLY");
727 EXPECT_EQ(fru2->failingComponentType(),
728 src::FRUIdentity::symbolicFRUTrustedLocCode);
729 }
730
731 {
732 // This time say we want to use the location code from
733 // the inventory, but don't pass it in and the callout should
734 // end up a regular symbolic FRU
735 entry.callouts = R"(
736 [{
737 "CalloutList":
738 [
739 {
740 "Priority": "high",
741 "SymbolicFRUTrusted": "service_docs",
742 "UseInventoryLocCode": true
743 }
744 ]
745 }])"_json;
746
747 AdditionalData ad;
748 NiceMock<MockDataInterface> dataIface;
749 std::vector<std::string> names{"systemA"};
750
751 EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
752
753 SRC src{entry, ad, dataIface};
754
755 auto& callouts = src.callouts()->callouts();
756 EXPECT_EQ(callouts.size(), 1);
757
758 EXPECT_EQ(callouts[0]->locationCode(), "");
759 EXPECT_EQ(callouts[0]->priority(), 'H');
760 auto& fru1 = callouts[0]->fruIdentity();
761 EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
762 EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRU);
763 }
764 }
765
TEST_F(SRCTest,RegistryCalloutCantGetLocTest)766 TEST_F(SRCTest, RegistryCalloutCantGetLocTest)
767 {
768 message::Entry entry;
769 entry.src.type = 0xBD;
770 entry.src.reasonCode = 0xABCD;
771 entry.src.deconfigFlag = true;
772 entry.src.checkstopFlag = true;
773 entry.subsystem = 0x42;
774
775 entry.callouts = R"(
776 [{
777 "CalloutList":
778 [
779 {
780 "Priority": "high",
781 "LocCode": "P0-C8"
782 },
783 {
784 "Priority": "medium",
785 "LocCode": "P0-C9"
786 }
787 ]
788 }])"_json;
789
790 {
791 // The calls to expand the location codes will fail, but it should
792 // still create the callouts with the unexpanded values and no HW
793 // fields.
794 AdditionalData ad;
795 NiceMock<MockDataInterface> dataIface;
796 std::vector<std::string> names{"systemC"};
797
798 EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
799
800 EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
801 .WillRepeatedly(Throw(std::runtime_error("Fail")));
802
803 EXPECT_CALL(dataIface, expandLocationCode("P0-C9", 0))
804 .WillRepeatedly(Throw(std::runtime_error("Fail")));
805
806 EXPECT_CALL(dataIface, getInventoryFromLocCode(_, _, _)).Times(0);
807
808 EXPECT_CALL(dataIface, getHWCalloutFields(_, _, _, _)).Times(0);
809
810 SRC src{entry, ad, dataIface};
811
812 auto& callouts = src.callouts()->callouts();
813 ASSERT_EQ(callouts.size(), 2);
814
815 // Only unexpanded location codes
816 EXPECT_EQ(callouts[0]->locationCode(), "P0-C8");
817 EXPECT_EQ(callouts[0]->priority(), 'H');
818
819 auto& fru1 = callouts[0]->fruIdentity();
820 EXPECT_EQ(fru1->getPN().value(), "");
821 EXPECT_EQ(fru1->getCCIN().value(), "");
822 EXPECT_EQ(fru1->getSN().value(), "");
823
824 EXPECT_EQ(callouts[1]->locationCode(), "P0-C9");
825 EXPECT_EQ(callouts[1]->priority(), 'M');
826
827 auto& fru2 = callouts[1]->fruIdentity();
828 EXPECT_EQ(fru2->getPN().value(), "");
829 EXPECT_EQ(fru2->getCCIN().value(), "");
830 EXPECT_EQ(fru2->getSN().value(), "");
831 }
832 }
833
TEST_F(SRCTest,TrustedSymbolicFRUCantGetLocTest)834 TEST_F(SRCTest, TrustedSymbolicFRUCantGetLocTest)
835 {
836 message::Entry entry;
837 entry.src.type = 0xBD;
838 entry.src.reasonCode = 0xABCD;
839 entry.subsystem = 0x42;
840
841 entry.callouts = R"(
842 [{
843 "CalloutList":
844 [
845 {
846 "Priority": "medium",
847 "LocCode": "P0-C8",
848 "SymbolicFRUTrusted": "pwrsply"
849 }
850 ]
851 }])"_json;
852
853 std::map<std::string, std::string> adData;
854 AdditionalData ad{adData};
855 NiceMock<MockDataInterface> dataIface;
856 std::vector<std::string> names{"systemA"};
857
858 EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
859
860 // The call to expand the location code will fail, but it should
861 // still create the callout with the unexpanded value and the
862 // symbolic FRU can't be trusted.
863 EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
864 .WillRepeatedly(Throw(std::runtime_error("Fail")));
865
866 SRC src{entry, ad, dataIface};
867
868 auto& callouts = src.callouts()->callouts();
869 ASSERT_EQ(callouts.size(), 1);
870
871 EXPECT_EQ(callouts[0]->locationCode(), "P0-C8");
872 EXPECT_EQ(callouts[0]->priority(), 'M');
873 auto& fru = callouts[0]->fruIdentity();
874 EXPECT_EQ(fru->getPN().value(), "PWRSPLY");
875 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
876 }
877
878 // Test looking up device path fails in the callout jSON.
TEST_F(SRCTest,DevicePathCalloutTest)879 TEST_F(SRCTest, DevicePathCalloutTest)
880 {
881 message::Entry entry;
882 entry.src.type = 0xBD;
883 entry.src.reasonCode = 0xABCD;
884 entry.subsystem = 0x42;
885
886 const auto calloutJSON = R"(
887 {
888 "I2C":
889 {
890 "14":
891 {
892 "114":
893 {
894 "Callouts":[
895 {
896 "Name": "/chassis/motherboard/cpu0",
897 "LocationCode": "P1-C40",
898 "Priority": "H"
899 },
900 {
901 "Name": "/chassis/motherboard",
902 "LocationCode": "P1",
903 "Priority": "M"
904 },
905 {
906 "Name": "/chassis/motherboard/bmc",
907 "LocationCode": "P1-C15",
908 "Priority": "L"
909 }
910 ],
911 "Dest": "proc 0 target"
912 }
913 }
914 }
915 })";
916
917 auto dataPath = getPELReadOnlyDataPath();
918 std::ofstream file{dataPath / "systemA_dev_callouts.json"};
919 file << calloutJSON;
920 file.close();
921
922 NiceMock<MockDataInterface> dataIface;
923 std::vector<std::string> names{"systemA"};
924
925 EXPECT_CALL(dataIface, getSystemNames)
926 .Times(5)
927 .WillRepeatedly(Return(names));
928
929 EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C40", 0, false))
930 .Times(3)
931 .WillRepeatedly(Return(std::vector<std::string>{
932 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"}));
933
934 EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
935 .Times(3)
936 .WillRepeatedly(Return(std::vector<std::string>{
937 "/xyz/openbmc_project/inventory/chassis/motherboard"}));
938
939 EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C15", 0, false))
940 .Times(3)
941 .WillRepeatedly(Return(std::vector<std::string>{
942 "/xyz/openbmc_project/inventory/chassis/motherboard/bmc"}));
943
944 EXPECT_CALL(dataIface, expandLocationCode("P1-C40", 0))
945 .Times(3)
946 .WillRepeatedly(Return("Ufcs-P1-C40"));
947
948 EXPECT_CALL(dataIface, expandLocationCode("P1", 0))
949 .Times(3)
950 .WillRepeatedly(Return("Ufcs-P1"));
951
952 EXPECT_CALL(dataIface, expandLocationCode("P1-C15", 0))
953 .Times(3)
954 .WillRepeatedly(Return("Ufcs-P1-C15"));
955
956 EXPECT_CALL(dataIface,
957 getHWCalloutFields(
958 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0",
959 _, _, _))
960 .Times(3)
961 .WillRepeatedly(
962 DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
963 SetArgReferee<3>("123456789ABC")));
964 EXPECT_CALL(
965 dataIface,
966 getHWCalloutFields("/xyz/openbmc_project/inventory/chassis/motherboard",
967 _, _, _))
968 .Times(3)
969 .WillRepeatedly(
970 DoAll(SetArgReferee<1>("7654321"), SetArgReferee<2>("MMMM"),
971 SetArgReferee<3>("CBA987654321")));
972 EXPECT_CALL(dataIface,
973 getHWCalloutFields(
974 "/xyz/openbmc_project/inventory/chassis/motherboard/bmc", _,
975 _, _))
976 .Times(3)
977 .WillRepeatedly(
978 DoAll(SetArgReferee<1>("7123456"), SetArgReferee<2>("BBBB"),
979 SetArgReferee<3>("C123456789AB")));
980
981 // Call this below with different AdditionalData values that
982 // result in the same callouts.
983 auto checkCallouts = [&entry, &dataIface](const auto& items) {
984 AdditionalData ad{items};
985 SRC src{entry, ad, dataIface};
986
987 ASSERT_TRUE(src.callouts());
988 auto& callouts = src.callouts()->callouts();
989
990 ASSERT_EQ(callouts.size(), 3);
991
992 {
993 EXPECT_EQ(callouts[0]->priority(), 'H');
994 EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P1-C40");
995
996 auto& fru = callouts[0]->fruIdentity();
997 EXPECT_EQ(fru->getPN().value(), "1234567");
998 EXPECT_EQ(fru->getCCIN().value(), "CCCC");
999 EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1000 }
1001 {
1002 EXPECT_EQ(callouts[1]->priority(), 'M');
1003 EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P1");
1004
1005 auto& fru = callouts[1]->fruIdentity();
1006 EXPECT_EQ(fru->getPN().value(), "7654321");
1007 EXPECT_EQ(fru->getCCIN().value(), "MMMM");
1008 EXPECT_EQ(fru->getSN().value(), "CBA987654321");
1009 }
1010 {
1011 EXPECT_EQ(callouts[2]->priority(), 'L');
1012 EXPECT_EQ(callouts[2]->locationCode(), "Ufcs-P1-C15");
1013
1014 auto& fru = callouts[2]->fruIdentity();
1015 EXPECT_EQ(fru->getPN().value(), "7123456");
1016 EXPECT_EQ(fru->getCCIN().value(), "BBBB");
1017 EXPECT_EQ(fru->getSN().value(), "C123456789AB");
1018 }
1019 };
1020
1021 {
1022 // Callouts based on the device path
1023 std::map<std::string, std::string> items{
1024 {"CALLOUT_ERRNO", "5"},
1025 {"CALLOUT_DEVICE_PATH",
1026 "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"}};
1027
1028 checkCallouts(items);
1029 }
1030
1031 {
1032 // Callouts based on the I2C bus and address
1033 std::map<std::string, std::string> items{{"CALLOUT_ERRNO", "5"},
1034 {"CALLOUT_IIC_BUS", "14"},
1035 {"CALLOUT_IIC_ADDR", "0x72"}};
1036 checkCallouts(items);
1037 }
1038
1039 {
1040 // Also based on I2C bus and address, but with bus = /dev/i2c-14
1041 std::map<std::string, std::string> items{{"CALLOUT_ERRNO", "5"},
1042 {"CALLOUT_IIC_BUS", "14"},
1043 {"CALLOUT_IIC_ADDR", "0x72"}};
1044 checkCallouts(items);
1045 }
1046
1047 {
1048 // Callout not found
1049 std::map<std::string, std::string> items{
1050 {"CALLOUT_ERRNO", "5"},
1051 {"CALLOUT_DEVICE_PATH",
1052 "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-24/24-0012"}};
1053
1054 AdditionalData ad{items};
1055 SRC src{entry, ad, dataIface};
1056
1057 EXPECT_FALSE(src.callouts());
1058 ASSERT_EQ(src.getDebugData().size(), 1);
1059 EXPECT_EQ(src.getDebugData()[0],
1060 "Problem looking up I2C callouts on 24 18: "
1061 "[json.exception.out_of_range.403] key '24' not found");
1062 }
1063
1064 {
1065 // Callout not found
1066 std::map<std::string, std::string> items{{"CALLOUT_ERRNO", "5"},
1067 {"CALLOUT_IIC_BUS", "22"},
1068 {"CALLOUT_IIC_ADDR", "0x99"}};
1069 AdditionalData ad{items};
1070 SRC src{entry, ad, dataIface};
1071
1072 EXPECT_FALSE(src.callouts());
1073 ASSERT_EQ(src.getDebugData().size(), 1);
1074 EXPECT_EQ(src.getDebugData()[0],
1075 "Problem looking up I2C callouts on 22 153: "
1076 "[json.exception.out_of_range.403] key '22' not found");
1077 }
1078 }
1079
TEST_F(SRCTest,DevicePathCantGetLocTest)1080 TEST_F(SRCTest, DevicePathCantGetLocTest)
1081 {
1082 message::Entry entry;
1083 entry.src.type = 0xBD;
1084 entry.src.reasonCode = 0xABCD;
1085 entry.subsystem = 0x42;
1086
1087 const auto calloutJSON = R"(
1088 {
1089 "I2C":
1090 {
1091 "14":
1092 {
1093 "114":
1094 {
1095 "Callouts":[
1096 {
1097 "Name": "/chassis/motherboard/cpu0",
1098 "LocationCode": "P1-C40",
1099 "Priority": "H"
1100 },
1101 {
1102 "Name": "/chassis/motherboard",
1103 "LocationCode": "P1",
1104 "Priority": "M"
1105 }
1106 ],
1107 "Dest": "proc 0 target"
1108 }
1109 }
1110 }
1111 })";
1112
1113 auto dataPath = getPELReadOnlyDataPath();
1114 std::ofstream file{dataPath / "systemA_dev_callouts.json"};
1115 file << calloutJSON;
1116 file.close();
1117
1118 NiceMock<MockDataInterface> dataIface;
1119 std::vector<std::string> names{"systemA"};
1120
1121 EXPECT_CALL(dataIface, getSystemNames).WillRepeatedly(Return(names));
1122
1123 // The calls to expand the location codes will fail, so still create
1124 // the callouts with the unexpanded values and no HW fields
1125
1126 EXPECT_CALL(dataIface, expandLocationCode("P1-C40", 0))
1127 .WillRepeatedly(Throw(std::runtime_error("Fail")));
1128
1129 EXPECT_CALL(dataIface, expandLocationCode("P1", 0))
1130 .WillRepeatedly(Throw(std::runtime_error("Fail")));
1131
1132 EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C40", 0, false))
1133 .Times(0);
1134 EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false)).Times(0);
1135
1136 std::map<std::string, std::string> items{
1137 {"CALLOUT_ERRNO", "5"},
1138 {"CALLOUT_DEVICE_PATH",
1139 "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"}};
1140
1141 AdditionalData ad{items};
1142 SRC src{entry, ad, dataIface};
1143
1144 ASSERT_TRUE(src.callouts());
1145 auto& callouts = src.callouts()->callouts();
1146
1147 ASSERT_EQ(callouts.size(), 2);
1148
1149 // Should just contain the unexpanded location codes
1150 {
1151 EXPECT_EQ(callouts[0]->priority(), 'H');
1152 EXPECT_EQ(callouts[0]->locationCode(), "P1-C40");
1153
1154 auto& fru = callouts[0]->fruIdentity();
1155 EXPECT_EQ(fru->getPN().value(), "");
1156 EXPECT_EQ(fru->getCCIN().value(), "");
1157 EXPECT_EQ(fru->getSN().value(), "");
1158 }
1159 {
1160 EXPECT_EQ(callouts[1]->priority(), 'M');
1161 EXPECT_EQ(callouts[1]->locationCode(), "P1");
1162
1163 auto& fru = callouts[1]->fruIdentity();
1164 EXPECT_EQ(fru->getPN().value(), "");
1165 EXPECT_EQ(fru->getCCIN().value(), "");
1166 EXPECT_EQ(fru->getSN().value(), "");
1167 }
1168
1169 fs::remove_all(dataPath);
1170 }
1171
1172 // Test when callouts are passed in via JSON
TEST_F(SRCTest,JsonCalloutsTest)1173 TEST_F(SRCTest, JsonCalloutsTest)
1174 {
1175 const auto jsonCallouts = R"(
1176 [
1177 {
1178 "LocationCode": "P0-C1",
1179 "Priority": "H",
1180 "MRUs": [
1181 {
1182 "ID": 42,
1183 "Priority": "H"
1184 },
1185 {
1186 "ID": 43,
1187 "Priority": "M"
1188 }
1189 ]
1190 },
1191 {
1192 "InventoryPath": "/inv/system/chassis/motherboard/cpu0",
1193 "Priority": "M",
1194 "Guarded": true,
1195 "Deconfigured": true
1196 },
1197 {
1198 "Procedure": "PROCEDU",
1199 "Priority": "A"
1200 },
1201 {
1202 "SymbolicFRU": "TRUSTED",
1203 "Priority": "B",
1204 "TrustedLocationCode": true,
1205 "LocationCode": "P1-C23"
1206 },
1207 {
1208 "SymbolicFRU": "FRUTST1",
1209 "Priority": "C",
1210 "LocationCode": "P1-C24"
1211 },
1212 {
1213 "SymbolicFRU": "FRUTST2LONG",
1214 "Priority": "L"
1215 },
1216 {
1217 "Procedure": "fsi_path",
1218 "Priority": "L"
1219 },
1220 {
1221 "SymbolicFRU": "ambient_temp",
1222 "Priority": "L"
1223 }
1224 ]
1225 )"_json;
1226
1227 message::Entry entry;
1228 entry.src.type = 0xBD;
1229 entry.src.reasonCode = 0xABCD;
1230 entry.subsystem = 0x42;
1231
1232 AdditionalData ad;
1233 NiceMock<MockDataInterface> dataIface;
1234
1235 // Callout 0 mock calls
1236 {
1237 EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1238 .Times(1)
1239 .WillOnce(Return("UXXX-P0-C1"));
1240 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1241 .Times(1)
1242 .WillOnce(Return(std::vector<std::string>{
1243 "/inv/system/chassis/motherboard/bmc"}));
1244 EXPECT_CALL(
1245 dataIface,
1246 getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1247 .Times(1)
1248 .WillOnce(
1249 DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1250 SetArgReferee<3>("123456789ABC")));
1251 }
1252 // Callout 1 mock calls
1253 {
1254 EXPECT_CALL(dataIface,
1255 getLocationCode("/inv/system/chassis/motherboard/cpu0"))
1256 .WillOnce(Return("UYYY-P5"));
1257 EXPECT_CALL(
1258 dataIface,
1259 getHWCalloutFields("/inv/system/chassis/motherboard/cpu0", _, _, _))
1260 .Times(1)
1261 .WillOnce(
1262 DoAll(SetArgReferee<1>("2345678"), SetArgReferee<2>("DDDD"),
1263 SetArgReferee<3>("23456789ABCD")));
1264 }
1265 // Callout 3 mock calls
1266 {
1267 EXPECT_CALL(dataIface, expandLocationCode("P1-C23", 0))
1268 .Times(1)
1269 .WillOnce(Return("UXXX-P1-C23"));
1270 }
1271 // Callout 4 mock calls
1272 {
1273 EXPECT_CALL(dataIface, expandLocationCode("P1-C24", 0))
1274 .Times(1)
1275 .WillOnce(Return("UXXX-P1-C24"));
1276 }
1277
1278 SRC src{entry, ad, jsonCallouts, dataIface};
1279 ASSERT_TRUE(src.callouts());
1280
1281 // Check the guarded and deconfigured flags
1282 EXPECT_TRUE(src.hexwordData()[3] & 0x03000000);
1283
1284 const auto& callouts = src.callouts()->callouts();
1285 ASSERT_EQ(callouts.size(), 8);
1286
1287 // Check callout 0
1288 {
1289 EXPECT_EQ(callouts[0]->priority(), 'H');
1290 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1291
1292 auto& fru = callouts[0]->fruIdentity();
1293 EXPECT_EQ(fru->getPN().value(), "1234567");
1294 EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1295 EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1296 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1297
1298 auto& mruCallouts = callouts[0]->mru();
1299 ASSERT_TRUE(mruCallouts);
1300 auto& mrus = mruCallouts->mrus();
1301 ASSERT_EQ(mrus.size(), 2);
1302 EXPECT_EQ(mrus[0].id, 42);
1303 EXPECT_EQ(mrus[0].priority, 'H');
1304 EXPECT_EQ(mrus[1].id, 43);
1305 EXPECT_EQ(mrus[1].priority, 'M');
1306 }
1307
1308 // Check callout 1
1309 {
1310 EXPECT_EQ(callouts[1]->priority(), 'M');
1311 EXPECT_EQ(callouts[1]->locationCode(), "UYYY-P5");
1312
1313 auto& fru = callouts[1]->fruIdentity();
1314 EXPECT_EQ(fru->getPN().value(), "2345678");
1315 EXPECT_EQ(fru->getCCIN().value(), "DDDD");
1316 EXPECT_EQ(fru->getSN().value(), "23456789ABCD");
1317 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1318 }
1319
1320 // Check callout 2
1321 {
1322 EXPECT_EQ(callouts[2]->priority(), 'A');
1323 EXPECT_EQ(callouts[2]->locationCode(), "");
1324
1325 auto& fru = callouts[2]->fruIdentity();
1326 EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1327 EXPECT_EQ(fru->failingComponentType(),
1328 src::FRUIdentity::maintenanceProc);
1329 }
1330
1331 // Check callout 3
1332 {
1333 EXPECT_EQ(callouts[3]->priority(), 'B');
1334 EXPECT_EQ(callouts[3]->locationCode(), "UXXX-P1-C23");
1335
1336 auto& fru = callouts[3]->fruIdentity();
1337 EXPECT_EQ(fru->getPN().value(), "TRUSTED");
1338 EXPECT_EQ(fru->failingComponentType(),
1339 src::FRUIdentity::symbolicFRUTrustedLocCode);
1340 }
1341
1342 // Check callout 4
1343 {
1344 EXPECT_EQ(callouts[4]->priority(), 'C');
1345 EXPECT_EQ(callouts[4]->locationCode(), "UXXX-P1-C24");
1346
1347 auto& fru = callouts[4]->fruIdentity();
1348 EXPECT_EQ(fru->getPN().value(), "FRUTST1");
1349 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1350 }
1351
1352 // Check callout 5
1353 {
1354 EXPECT_EQ(callouts[5]->priority(), 'L');
1355 EXPECT_EQ(callouts[5]->locationCode(), "");
1356
1357 auto& fru = callouts[5]->fruIdentity();
1358 EXPECT_EQ(fru->getPN().value(), "FRUTST2");
1359 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1360 }
1361
1362 // Check callout 6
1363 {
1364 EXPECT_EQ(callouts[6]->priority(), 'L');
1365 EXPECT_EQ(callouts[6]->locationCode(), "");
1366
1367 auto& fru = callouts[6]->fruIdentity();
1368 EXPECT_EQ(fru->getMaintProc().value(), "BMC0004");
1369 EXPECT_EQ(fru->failingComponentType(),
1370 src::FRUIdentity::maintenanceProc);
1371 }
1372
1373 // Check callout 7
1374 {
1375 EXPECT_EQ(callouts[7]->priority(), 'L');
1376 EXPECT_EQ(callouts[7]->locationCode(), "");
1377
1378 auto& fru = callouts[7]->fruIdentity();
1379 EXPECT_EQ(fru->getPN().value(), "AMBTEMP");
1380 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1381 }
1382
1383 // Check that it didn't find any errors
1384 const auto& data = src.getDebugData();
1385 EXPECT_TRUE(data.empty());
1386 }
1387
TEST_F(SRCTest,JsonBadCalloutsTest)1388 TEST_F(SRCTest, JsonBadCalloutsTest)
1389 {
1390 // The first call will have a Throw in a mock call.
1391 // The second will have a different Throw in a mock call.
1392 // The others have issues with the Priority field.
1393 const auto jsonCallouts = R"(
1394 [
1395 {
1396 "LocationCode": "P0-C1",
1397 "Priority": "H"
1398 },
1399 {
1400 "LocationCode": "P0-C2",
1401 "Priority": "H"
1402 },
1403 {
1404 "LocationCode": "P0-C3"
1405 },
1406 {
1407 "LocationCode": "P0-C4",
1408 "Priority": "X"
1409 }
1410 ]
1411 )"_json;
1412
1413 message::Entry entry;
1414 entry.src.type = 0xBD;
1415 entry.src.reasonCode = 0xABCD;
1416 entry.subsystem = 0x42;
1417
1418 AdditionalData ad;
1419 NiceMock<MockDataInterface> dataIface;
1420
1421 // Callout 0 mock calls
1422 // Expand location code will fail, so the unexpanded location
1423 // code should show up in the callout instead.
1424 {
1425 EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1426 .WillOnce(Throw(std::runtime_error("Fail")));
1427
1428 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1429 .Times(1)
1430 .WillOnce(Return(std::vector<std::string>{
1431 "/inv/system/chassis/motherboard/bmc"}));
1432 EXPECT_CALL(
1433 dataIface,
1434 getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1435 .Times(1)
1436 .WillOnce(
1437 DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1438 SetArgReferee<3>("123456789ABC")));
1439 }
1440
1441 // Callout 1 mock calls
1442 // getInventoryFromLocCode will fail, so a callout with just the
1443 // location code will be created.
1444 {
1445 EXPECT_CALL(dataIface, expandLocationCode("P0-C2", 0))
1446 .Times(1)
1447 .WillOnce(Return("UXXX-P0-C2"));
1448
1449 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C2", 0, false))
1450 .Times(1)
1451 .WillOnce(Throw(std::runtime_error("Fail")));
1452 }
1453
1454 SRC src{entry, ad, jsonCallouts, dataIface};
1455
1456 ASSERT_TRUE(src.callouts());
1457
1458 const auto& callouts = src.callouts()->callouts();
1459
1460 // The first callout will have the unexpanded location code.
1461 ASSERT_EQ(callouts.size(), 2);
1462
1463 EXPECT_EQ(callouts[0]->priority(), 'H');
1464 EXPECT_EQ(callouts[0]->locationCode(), "P0-C1");
1465
1466 auto& fru0 = callouts[0]->fruIdentity();
1467 EXPECT_EQ(fru0->getPN().value(), "1234567");
1468 EXPECT_EQ(fru0->getCCIN().value(), "CCCC");
1469 EXPECT_EQ(fru0->getSN().value(), "123456789ABC");
1470 EXPECT_EQ(fru0->failingComponentType(), src::FRUIdentity::hardwareFRU);
1471
1472 // The second callout will have empty HW details.
1473 EXPECT_EQ(callouts[1]->priority(), 'H');
1474 EXPECT_EQ(callouts[1]->locationCode(), "UXXX-P0-C2");
1475
1476 auto& fru1 = callouts[1]->fruIdentity();
1477 EXPECT_EQ(fru1->getPN().value(), "");
1478 EXPECT_EQ(fru1->getCCIN().value(), "");
1479 EXPECT_EQ(fru1->getSN().value(), "");
1480 EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::hardwareFRU);
1481
1482 const auto& data = src.getDebugData();
1483 ASSERT_EQ(data.size(), 4);
1484 EXPECT_STREQ(data[0].c_str(), "Unable to expand location code P0-C1: Fail");
1485 EXPECT_STREQ(
1486 data[1].c_str(),
1487 "Unable to get inventory path from location code: P0-C2: Fail");
1488 EXPECT_STREQ(data[2].c_str(),
1489 "Failed extracting callout data from JSON: "
1490 "[json.exception.out_of_range.403] key 'Priority' not found");
1491 EXPECT_STREQ(data[3].c_str(),
1492 "Failed extracting callout data from JSON: Invalid "
1493 "priority 'X' found in JSON callout");
1494 }
1495
1496 // Test that an inventory path callout can have
1497 // a different priority than H.
TEST_F(SRCTest,InventoryCalloutTestPriority)1498 TEST_F(SRCTest, InventoryCalloutTestPriority)
1499 {
1500 message::Entry entry;
1501 entry.src.type = 0xBD;
1502 entry.src.reasonCode = 0xABCD;
1503 entry.subsystem = 0x42;
1504
1505 std::map<std::string, std::string> adData{
1506 {"CALLOUT_INVENTORY_PATH", "motherboard"}, {"CALLOUT_PRIORITY", "M"}};
1507 AdditionalData ad{adData};
1508 NiceMock<MockDataInterface> dataIface;
1509
1510 EXPECT_CALL(dataIface, getLocationCode("motherboard"))
1511 .WillOnce(Return("UTMS-P1"));
1512
1513 EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
1514 .Times(1)
1515 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1516 SetArgReferee<3>("123456789ABC")));
1517
1518 SRC src{entry, ad, dataIface};
1519 EXPECT_TRUE(src.valid());
1520
1521 ASSERT_TRUE(src.callouts());
1522
1523 EXPECT_EQ(src.callouts()->callouts().size(), 1);
1524
1525 auto& callout = src.callouts()->callouts().front();
1526
1527 EXPECT_EQ(callout->locationCode(), "UTMS-P1");
1528 EXPECT_EQ(callout->priority(), 'M');
1529 }
1530
1531 // Test SRC with additional data - PEL_SUBSYSTEM
TEST_F(SRCTest,TestPELSubsystem)1532 TEST_F(SRCTest, TestPELSubsystem)
1533 {
1534 message::Entry entry;
1535 entry.src.type = 0xBD;
1536 entry.src.reasonCode = 0xABCD;
1537 entry.subsystem = 0x42;
1538
1539 // Values for the SRC words pointed to above
1540 std::map<std::string, std::string> adData{{"PEL_SUBSYSTEM", "0x20"}};
1541 AdditionalData ad{adData};
1542 NiceMock<MockDataInterface> dataIface;
1543
1544 EXPECT_CALL(dataIface, getMotherboardCCIN).WillOnce(Return("ABCD"));
1545
1546 SRC src{entry, ad, dataIface};
1547
1548 EXPECT_TRUE(src.valid());
1549
1550 EXPECT_EQ(src.asciiString(), "BD20ABCD ");
1551 }
1552
setAsciiString(std::vector<uint8_t> & src,const std::string & value)1553 void setAsciiString(std::vector<uint8_t>& src, const std::string& value)
1554 {
1555 assert(40 + value.size() <= src.size());
1556
1557 for (size_t i = 0; i < value.size(); i++)
1558 {
1559 src[40 + i] = value[i];
1560 }
1561 }
1562
TEST_F(SRCTest,TestGetProgressCode)1563 TEST_F(SRCTest, TestGetProgressCode)
1564 {
1565 {
1566 // A real SRC with CC009184
1567 std::vector<uint8_t> src{
1568 2, 8, 0, 9, 0, 0, 0, 72, 0, 0, 0, 224, 0, 0, 0,
1569 0, 204, 0, 145, 132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1570 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 48, 48, 57,
1571 49, 56, 52, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
1572 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32};
1573
1574 EXPECT_EQ(SRC::getProgressCode(src), 0xCC009184);
1575 }
1576
1577 {
1578 // A real SRC with STANDBY
1579 std::vector<uint8_t> src{
1580 2, 0, 0, 1, 0, 0, 0, 72, 0, 0, 0, 0, 0, 0, 0,
1581 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1582 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 84, 65, 78, 68,
1583 66, 89, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
1584 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32};
1585
1586 EXPECT_EQ(SRC::getProgressCode(src), 0);
1587 }
1588
1589 {
1590 // A real SRC with CC009184, but 1 byte too short
1591 std::vector<uint8_t> src{
1592 2, 8, 0, 9, 0, 0, 0, 72, 0, 0, 0, 224, 0, 0, 0,
1593 0, 204, 0, 145, 132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1594 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 48, 48, 57,
1595 49, 56, 52, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
1596 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32};
1597
1598 EXPECT_EQ(SRC::getProgressCode(src), 0);
1599 }
1600
1601 {
1602 // A few different ones
1603 const std::map<std::string, uint32_t> progressCodes{
1604 {"12345678", 0x12345678}, {"ABCDEF00", 0xABCDEF00},
1605 {"abcdef00", 0xABCDEF00}, {"X1234567", 0},
1606 {"1234567X", 0}, {"1 ", 0}};
1607
1608 std::vector<uint8_t> src(72, 0x0);
1609
1610 for (const auto& [code, expected] : progressCodes)
1611 {
1612 setAsciiString(src, code);
1613 EXPECT_EQ(SRC::getProgressCode(src), expected);
1614 }
1615
1616 // empty
1617 src.clear();
1618 EXPECT_EQ(SRC::getProgressCode(src), 0);
1619 }
1620 }
1621
1622 // Test progress is in right SRC hex data field
TEST_F(SRCTest,TestProgressCodeField)1623 TEST_F(SRCTest, TestProgressCodeField)
1624 {
1625 message::Entry entry;
1626 entry.src.type = 0xBD;
1627 entry.src.reasonCode = 0xABCD;
1628 entry.subsystem = 0x42;
1629
1630 AdditionalData ad;
1631 NiceMock<MockDataInterface> dataIface;
1632 EXPECT_CALL(dataIface, getRawProgressSRC())
1633 .WillOnce(Return(std::vector<uint8_t>{
1634 2, 8, 0, 9, 0, 0, 0, 72, 0, 0, 0, 224, 0, 0, 0,
1635 0, 204, 0, 145, 132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1636 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 48, 48, 57,
1637 49, 56, 52, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
1638 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32}));
1639
1640 SRC src{entry, ad, dataIface};
1641 EXPECT_TRUE(src.valid());
1642
1643 // Verify that the hex vlue is set at the right hexword
1644 EXPECT_EQ(src.hexwordData()[2], 0xCC009184);
1645 }
1646