/** * Copyright © 2019 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "extensions/openpower-pels/src.hpp" #include "mocks.hpp" #include "pel_utils.hpp" #include #include using namespace openpower::pels; using ::testing::_; using ::testing::DoAll; using ::testing::InvokeWithoutArgs; using ::testing::NiceMock; using ::testing::Return; using ::testing::SetArgReferee; using ::testing::Throw; namespace fs = std::filesystem; const auto testRegistry = R"( { "PELs": [ { "Name": "xyz.openbmc_project.Error.Test", "Subsystem": "bmc_firmware", "SRC": { "ReasonCode": "0xABCD", "Words6To9": { "6": { "Description": "Component ID", "AdditionalDataPropSource": "COMPID" }, "7": { "Description": "Failure count", "AdditionalDataPropSource": "FREQUENCY" }, "8": { "Description": "Time period", "AdditionalDataPropSource": "DURATION" }, "9": { "Description": "Error code", "AdditionalDataPropSource": "ERRORCODE" } } }, "Documentation": { "Description": "A Component Fault", "Message": "Comp %1 failed %2 times over %3 secs with ErrorCode %4", "MessageArgSources": [ "SRCWord6", "SRCWord7", "SRCWord8", "SRCWord9" ] } } ] } )"; class SRCTest : public ::testing::Test { protected: static void SetUpTestCase() { char path[] = "/tmp/srctestXXXXXX"; regDir = mkdtemp(path); } static void TearDownTestCase() { fs::remove_all(regDir); } static std::string writeData(const char* data) { fs::path path = regDir / "registry.json"; std::ofstream stream{path}; stream << data; return path; } static fs::path regDir; }; fs::path SRCTest::regDir{}; TEST_F(SRCTest, UnflattenFlattenTestNoCallouts) { auto data = pelDataFactory(TestPELType::primarySRCSection); Stream stream{data}; SRC src{stream}; EXPECT_TRUE(src.valid()); EXPECT_EQ(src.header().id, 0x5053); EXPECT_EQ(src.header().size, 0x50); EXPECT_EQ(src.header().version, 0x01); EXPECT_EQ(src.header().subType, 0x01); EXPECT_EQ(src.header().componentID, 0x0202); EXPECT_EQ(src.version(), 0x02); EXPECT_EQ(src.flags(), 0x00); EXPECT_EQ(src.hexWordCount(), 9); EXPECT_EQ(src.size(), 0x48); const auto& hexwords = src.hexwordData(); EXPECT_EQ(0x02020255, hexwords[0]); EXPECT_EQ(0x03030310, hexwords[1]); EXPECT_EQ(0x04040404, hexwords[2]); EXPECT_EQ(0x05050505, hexwords[3]); EXPECT_EQ(0x06060606, hexwords[4]); EXPECT_EQ(0x07070707, hexwords[5]); EXPECT_EQ(0x08080808, hexwords[6]); EXPECT_EQ(0x09090909, hexwords[7]); EXPECT_EQ(src.asciiString(), "BD8D5678 "); EXPECT_FALSE(src.callouts()); // Flatten std::vector newData; Stream newStream{newData}; src.flatten(newStream); EXPECT_EQ(data, newData); } TEST_F(SRCTest, UnflattenFlattenTest2Callouts) { auto data = pelDataFactory(TestPELType::primarySRCSection2Callouts); Stream stream{data}; SRC src{stream}; EXPECT_TRUE(src.valid()); EXPECT_EQ(src.flags(), 0x01); // Additional sections within the SRC. // Spot check the SRC fields, but they're the same as above EXPECT_EQ(src.asciiString(), "BD8D5678 "); // There should be 2 callouts const auto& calloutsSection = src.callouts(); ASSERT_TRUE(calloutsSection); const auto& callouts = calloutsSection->callouts(); EXPECT_EQ(callouts.size(), 2); // spot check that each callout has the right substructures EXPECT_TRUE(callouts.front()->fruIdentity()); EXPECT_FALSE(callouts.front()->pceIdentity()); EXPECT_FALSE(callouts.front()->mru()); EXPECT_TRUE(callouts.back()->fruIdentity()); EXPECT_TRUE(callouts.back()->pceIdentity()); EXPECT_TRUE(callouts.back()->mru()); // Flatten std::vector newData; Stream newStream{newData}; src.flatten(newStream); EXPECT_EQ(data, newData); } // Create an SRC from the message registry TEST_F(SRCTest, CreateTestNoCallouts) { message::Entry entry; entry.src.type = 0xBD; entry.src.reasonCode = 0xABCD; entry.subsystem = 0x42; entry.src.powerFault = true; entry.src.hexwordADFields = { {5, {"TEST1", "DESCR1"}}, // Not a user defined word {6, {"TEST1", "DESCR1"}}, {7, {"TEST2", "DESCR2"}}, {8, {"TEST3", "DESCR3"}}, {9, {"TEST4", "DESCR4"}}}; // Values for the SRC words pointed to above std::vector adData{"TEST1=0x12345678", "TEST2=12345678", "TEST3=0XDEF", "TEST4=Z"}; AdditionalData ad{adData}; NiceMock dataIface; EXPECT_CALL(dataIface, getMotherboardCCIN).WillOnce(Return("ABCD")); SRC src{entry, ad, dataIface}; EXPECT_TRUE(src.valid()); EXPECT_TRUE(src.isPowerFaultEvent()); EXPECT_EQ(src.size(), baseSRCSize); const auto& hexwords = src.hexwordData(); // The spec always refers to SRC words 2 - 9, and as the hexwordData() // array index starts at 0 use the math in the [] below to make it easier // to tell what is being accessed. EXPECT_EQ(hexwords[2 - 2] & 0xF0000000, 0); // Partition dump status EXPECT_EQ(hexwords[2 - 2] & 0x00F00000, 0); // Partition boot type EXPECT_EQ(hexwords[2 - 2] & 0x000000FF, 0x55); // SRC format EXPECT_EQ(hexwords[3 - 2] & 0x000000FF, 0x10); // BMC position EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0xABCD0000); // Motherboard CCIN // Validate more fields here as the code starts filling them in. // Ensure hex word 5 wasn't allowed to be set to TEST1's contents EXPECT_EQ(hexwords[5 - 2], 0); // The user defined hex word fields specifed in the additional data. EXPECT_EQ(hexwords[6 - 2], 0x12345678); // TEST1 EXPECT_EQ(hexwords[7 - 2], 12345678); // TEST2 EXPECT_EQ(hexwords[8 - 2], 0xdef); // TEST3 EXPECT_EQ(hexwords[9 - 2], 0); // TEST4, but can't convert a 'Z' EXPECT_EQ(src.asciiString(), "BD42ABCD "); // No callouts EXPECT_FALSE(src.callouts()); // May as well spot check the flatten/unflatten std::vector data; Stream stream{data}; src.flatten(stream); stream.offset(0); SRC newSRC{stream}; EXPECT_TRUE(newSRC.valid()); EXPECT_EQ(newSRC.isPowerFaultEvent(), src.isPowerFaultEvent()); EXPECT_EQ(newSRC.asciiString(), src.asciiString()); EXPECT_FALSE(newSRC.callouts()); } // Create an SRC to test POWER_THERMAL_CRITICAL_FAULT set to TRUE // sets the power fault bit in SRC TEST_F(SRCTest, PowerFaultTest) { message::Entry entry; entry.src.type = 0xBD; entry.src.reasonCode = 0xABCD; entry.subsystem = 0x42; entry.src.powerFault = false; // Values for the SRC words pointed to above std::vector adData{"POWER_THERMAL_CRITICAL_FAULT=TRUE", "TEST2=12345678", "TEST3=0XDEF", "TEST4=Z"}; AdditionalData ad{adData}; NiceMock dataIface; SRC src{entry, ad, dataIface}; EXPECT_TRUE(src.valid()); EXPECT_TRUE(src.isPowerFaultEvent()); EXPECT_EQ(src.size(), baseSRCSize); } // Test when the CCIN string isn't a 4 character number TEST_F(SRCTest, BadCCINTest) { message::Entry entry; entry.src.type = 0xBD; entry.src.reasonCode = 0xABCD; entry.subsystem = 0x42; entry.src.powerFault = false; std::vector adData{}; AdditionalData ad{adData}; NiceMock dataIface; // First it isn't a number, then it is too long, // then it is empty. EXPECT_CALL(dataIface, getMotherboardCCIN) .WillOnce(Return("X")) .WillOnce(Return("12345")) .WillOnce(Return("")); // The CCIN in the first half should still be 0 each time. { SRC src{entry, ad, dataIface}; EXPECT_TRUE(src.valid()); const auto& hexwords = src.hexwordData(); EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000); } { SRC src{entry, ad, dataIface}; EXPECT_TRUE(src.valid()); const auto& hexwords = src.hexwordData(); EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000); } { SRC src{entry, ad, dataIface}; EXPECT_TRUE(src.valid()); const auto& hexwords = src.hexwordData(); EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000); } } // Test the getErrorDetails function TEST_F(SRCTest, MessageSubstitutionTest) { auto path = SRCTest::writeData(testRegistry); message::Registry registry{path}; auto entry = registry.lookup("0xABCD", message::LookupType::reasonCode); std::vector adData{"COMPID=0x1", "FREQUENCY=0x4", "DURATION=30", "ERRORCODE=0x01ABCDEF"}; AdditionalData ad{adData}; NiceMock dataIface; SRC src{*entry, ad, dataIface}; EXPECT_TRUE(src.valid()); auto errorDetails = src.getErrorDetails(registry, DetailLevel::message); ASSERT_TRUE(errorDetails); EXPECT_EQ( errorDetails.value(), "Comp 0x1 failed 0x4 times over 0x1E secs with ErrorCode 0x1ABCDEF"); } // Test that an inventory path callout string is // converted into the appropriate FRU callout. TEST_F(SRCTest, InventoryCalloutTest) { message::Entry entry; entry.src.type = 0xBD; entry.src.reasonCode = 0xABCD; entry.subsystem = 0x42; entry.src.powerFault = false; std::vector adData{"CALLOUT_INVENTORY_PATH=motherboard"}; AdditionalData ad{adData}; NiceMock dataIface; EXPECT_CALL(dataIface, getLocationCode("motherboard")) .WillOnce(Return("UTMS-P1")); EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _)) .Times(1) .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), SetArgReferee<3>("123456789ABC"))); SRC src{entry, ad, dataIface}; EXPECT_TRUE(src.valid()); ASSERT_TRUE(src.callouts()); EXPECT_EQ(src.callouts()->callouts().size(), 1); auto& callout = src.callouts()->callouts().front(); EXPECT_EQ(callout->locationCode(), "UTMS-P1"); EXPECT_EQ(callout->priority(), 'H'); auto& fru = callout->fruIdentity(); EXPECT_EQ(fru->getPN().value(), "1234567"); EXPECT_EQ(fru->getCCIN().value(), "CCCC"); EXPECT_EQ(fru->getSN().value(), "123456789ABC"); // flatten and unflatten std::vector data; Stream stream{data}; src.flatten(stream); stream.offset(0); SRC newSRC{stream}; EXPECT_TRUE(newSRC.valid()); ASSERT_TRUE(src.callouts()); EXPECT_EQ(src.callouts()->callouts().size(), 1); } // Test that when the location code can't be obtained that // a procedure callout is used. TEST_F(SRCTest, InventoryCalloutNoLocCodeTest) { message::Entry entry; entry.src.type = 0xBD; entry.src.reasonCode = 0xABCD; entry.subsystem = 0x42; entry.src.powerFault = false; std::vector adData{"CALLOUT_INVENTORY_PATH=motherboard"}; AdditionalData ad{adData}; NiceMock dataIface; auto func = []() { throw sdbusplus::exception::SdBusError(5, "Error"); return std::string{}; }; EXPECT_CALL(dataIface, getLocationCode("motherboard")) .Times(1) .WillOnce(InvokeWithoutArgs(func)); EXPECT_CALL(dataIface, getHWCalloutFields(_, _, _, _)).Times(0); SRC src{entry, ad, dataIface}; EXPECT_TRUE(src.valid()); ASSERT_TRUE(src.callouts()); EXPECT_EQ(src.callouts()->callouts().size(), 1); auto& callout = src.callouts()->callouts().front(); EXPECT_EQ(callout->locationCodeSize(), 0); EXPECT_EQ(callout->priority(), 'H'); auto& fru = callout->fruIdentity(); EXPECT_EQ(fru->getMaintProc().value(), "BMCSP01"); EXPECT_FALSE(fru->getPN()); EXPECT_FALSE(fru->getSN()); EXPECT_FALSE(fru->getCCIN()); // flatten and unflatten std::vector data; Stream stream{data}; src.flatten(stream); stream.offset(0); SRC newSRC{stream}; EXPECT_TRUE(newSRC.valid()); ASSERT_TRUE(src.callouts()); EXPECT_EQ(src.callouts()->callouts().size(), 1); } // Test that when the VPD can't be obtained that // a callout is still created. TEST_F(SRCTest, InventoryCalloutNoVPDTest) { message::Entry entry; entry.src.type = 0xBD; entry.src.reasonCode = 0xABCD; entry.subsystem = 0x42; entry.src.powerFault = false; std::vector adData{"CALLOUT_INVENTORY_PATH=motherboard"}; AdditionalData ad{adData}; NiceMock dataIface; EXPECT_CALL(dataIface, getLocationCode("motherboard")) .Times(1) .WillOnce(Return("UTMS-P10")); auto func = []() { throw sdbusplus::exception::SdBusError(5, "Error"); }; EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _)) .Times(1) .WillOnce(InvokeWithoutArgs(func)); SRC src{entry, ad, dataIface}; EXPECT_TRUE(src.valid()); ASSERT_TRUE(src.callouts()); EXPECT_EQ(src.callouts()->callouts().size(), 1); auto& callout = src.callouts()->callouts().front(); EXPECT_EQ(callout->locationCode(), "UTMS-P10"); EXPECT_EQ(callout->priority(), 'H'); auto& fru = callout->fruIdentity(); EXPECT_EQ(fru->getPN(), ""); EXPECT_EQ(fru->getCCIN(), ""); EXPECT_EQ(fru->getSN(), ""); EXPECT_FALSE(fru->getMaintProc()); // flatten and unflatten std::vector data; Stream stream{data}; src.flatten(stream); stream.offset(0); SRC newSRC{stream}; EXPECT_TRUE(newSRC.valid()); ASSERT_TRUE(src.callouts()); EXPECT_EQ(src.callouts()->callouts().size(), 1); } TEST_F(SRCTest, RegistryCalloutTest) { message::Entry entry; entry.src.type = 0xBD; entry.src.reasonCode = 0xABCD; entry.subsystem = 0x42; entry.src.powerFault = false; entry.callouts = R"( [ { "System": "systemA", "CalloutList": [ { "Priority": "high", "SymbolicFRU": "service_docs" }, { "Priority": "medium", "Procedure": "no_vpd_for_fru" } ] }, { "System": "systemB", "CalloutList": [ { "Priority": "high", "LocCode": "P0-C8", "SymbolicFRUTrusted": "service_docs" }, { "Priority": "medium", "SymbolicFRUTrusted": "service_docs" } ] }, { "System": "systemC", "CalloutList": [ { "Priority": "high", "LocCode": "P0-C8" }, { "Priority": "medium", "LocCode": "P0-C9" } ] } ])"_json; { // Call out a symbolic FRU and a procedure AdditionalData ad; NiceMock dataIface; std::vector names{"systemA"}; EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names)); SRC src{entry, ad, dataIface}; auto& callouts = src.callouts()->callouts(); ASSERT_EQ(callouts.size(), 2); EXPECT_EQ(callouts[0]->locationCodeSize(), 0); EXPECT_EQ(callouts[0]->priority(), 'H'); EXPECT_EQ(callouts[1]->locationCodeSize(), 0); EXPECT_EQ(callouts[1]->priority(), 'M'); auto& fru1 = callouts[0]->fruIdentity(); EXPECT_EQ(fru1->getPN().value(), "SVCDOCS"); EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRU); EXPECT_FALSE(fru1->getMaintProc()); EXPECT_FALSE(fru1->getSN()); EXPECT_FALSE(fru1->getCCIN()); auto& fru2 = callouts[1]->fruIdentity(); EXPECT_EQ(fru2->getMaintProc().value(), "BMCSP01"); EXPECT_EQ(fru2->failingComponentType(), src::FRUIdentity::maintenanceProc); EXPECT_FALSE(fru2->getPN()); EXPECT_FALSE(fru2->getSN()); EXPECT_FALSE(fru2->getCCIN()); } { // Call out a trusted symbolic FRU with a location code, and // another one without. AdditionalData ad; NiceMock dataIface; std::vector names{"systemB"}; EXPECT_CALL(dataIface, expandLocationCode).WillOnce(Return("P0-C8")); EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names)); SRC src{entry, ad, dataIface}; auto& callouts = src.callouts()->callouts(); EXPECT_EQ(callouts.size(), 2); EXPECT_EQ(callouts[0]->locationCode(), "P0-C8"); EXPECT_EQ(callouts[0]->priority(), 'H'); EXPECT_EQ(callouts[1]->locationCodeSize(), 0); EXPECT_EQ(callouts[1]->priority(), 'M'); auto& fru1 = callouts[0]->fruIdentity(); EXPECT_EQ(fru1->getPN().value(), "SVCDOCS"); EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRUTrustedLocCode); EXPECT_FALSE(fru1->getMaintProc()); EXPECT_FALSE(fru1->getSN()); EXPECT_FALSE(fru1->getCCIN()); // It asked for a trusted symbolic FRU, but no location code // was provided so it is switched back to a normal one auto& fru2 = callouts[1]->fruIdentity(); EXPECT_EQ(fru2->getPN().value(), "SVCDOCS"); EXPECT_EQ(fru2->failingComponentType(), src::FRUIdentity::symbolicFRU); EXPECT_FALSE(fru2->getMaintProc()); EXPECT_FALSE(fru2->getSN()); EXPECT_FALSE(fru2->getCCIN()); } { // Two hardware callouts AdditionalData ad; NiceMock dataIface; std::vector names{"systemC"}; EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names)); EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0)) .WillOnce(Return("UXXX-P0-C8")); EXPECT_CALL(dataIface, expandLocationCode("P0-C9", 0)) .WillOnce(Return("UXXX-P0-C9")); EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C8", 0, false)) .WillOnce(Return( "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0")); EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C9", 0, false)) .WillOnce(Return( "/xyz/openbmc_project/inventory/chassis/motherboard/cpu1")); EXPECT_CALL( dataIface, getHWCalloutFields( "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _, _)) .Times(1) .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), SetArgReferee<3>("123456789ABC"))); EXPECT_CALL( dataIface, getHWCalloutFields( "/xyz/openbmc_project/inventory/chassis/motherboard/cpu1", _, _, _)) .Times(1) .WillOnce(DoAll(SetArgReferee<1>("2345678"), SetArgReferee<2>("DDDD"), SetArgReferee<3>("23456789ABCD"))); SRC src{entry, ad, dataIface}; auto& callouts = src.callouts()->callouts(); EXPECT_EQ(callouts.size(), 2); EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C8"); EXPECT_EQ(callouts[0]->priority(), 'H'); auto& fru1 = callouts[0]->fruIdentity(); EXPECT_EQ(fru1->getPN().value(), "1234567"); EXPECT_EQ(fru1->getCCIN().value(), "CCCC"); EXPECT_EQ(fru1->getSN().value(), "123456789ABC"); EXPECT_EQ(callouts[1]->locationCode(), "UXXX-P0-C9"); EXPECT_EQ(callouts[1]->priority(), 'M'); auto& fru2 = callouts[1]->fruIdentity(); EXPECT_EQ(fru2->getPN().value(), "2345678"); EXPECT_EQ(fru2->getCCIN().value(), "DDDD"); EXPECT_EQ(fru2->getSN().value(), "23456789ABCD"); } } // Test that a symbolic FRU with a trusted location code callout // from the registry can get its location from the // CALLOUT_INVENTORY_PATH AdditionalData entry. TEST_F(SRCTest, SymbolicFRUWithInvPathTest) { message::Entry entry; entry.src.type = 0xBD; entry.src.reasonCode = 0xABCD; entry.subsystem = 0x42; entry.src.powerFault = false; entry.callouts = R"( [{ "CalloutList": [ { "Priority": "high", "SymbolicFRUTrusted": "service_docs", "UseInventoryLocCode": true }, { "Priority": "medium", "LocCode": "P0-C8", "SymbolicFRUTrusted": "pwrsply" } ] }])"_json; { // The location code for the first symbolic FRU callout will // come from this inventory path since UseInventoryLocCode is set. // In this case there will be no normal FRU callout for the motherboard. std::vector adData{"CALLOUT_INVENTORY_PATH=motherboard"}; AdditionalData ad{adData}; NiceMock dataIface; std::vector names{"systemA"}; EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names)); EXPECT_CALL(dataIface, getLocationCode("motherboard")) .Times(1) .WillOnce(Return("Ufcs-P10")); EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0)) .WillOnce(Return("Ufcs-P0-C8")); SRC src{entry, ad, dataIface}; auto& callouts = src.callouts()->callouts(); EXPECT_EQ(callouts.size(), 2); // The location code for the first symbolic FRU callout with a // trusted location code comes from the motherboard. EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P10"); EXPECT_EQ(callouts[0]->priority(), 'H'); auto& fru1 = callouts[0]->fruIdentity(); EXPECT_EQ(fru1->getPN().value(), "SVCDOCS"); EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRUTrustedLocCode); // The second trusted symbolic FRU callouts uses the location // code in the registry as usual. EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P0-C8"); EXPECT_EQ(callouts[1]->priority(), 'M'); auto& fru2 = callouts[1]->fruIdentity(); EXPECT_EQ(fru2->getPN().value(), "PWRSPLY"); EXPECT_EQ(fru2->failingComponentType(), src::FRUIdentity::symbolicFRUTrustedLocCode); } { // This time say we want to use the location code from // the inventory, but don't pass it in and the callout should // end up a regular symbolic FRU entry.callouts = R"( [{ "CalloutList": [ { "Priority": "high", "SymbolicFRUTrusted": "service_docs", "UseInventoryLocCode": true } ] }])"_json; AdditionalData ad; NiceMock dataIface; std::vector names{"systemA"}; EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names)); SRC src{entry, ad, dataIface}; auto& callouts = src.callouts()->callouts(); EXPECT_EQ(callouts.size(), 1); EXPECT_EQ(callouts[0]->locationCode(), ""); EXPECT_EQ(callouts[0]->priority(), 'H'); auto& fru1 = callouts[0]->fruIdentity(); EXPECT_EQ(fru1->getPN().value(), "SVCDOCS"); EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRU); } } // Test looking up device path fails in the callout jSON. TEST_F(SRCTest, DevicePathCalloutTest) { message::Entry entry; entry.src.type = 0xBD; entry.src.reasonCode = 0xABCD; entry.subsystem = 0x42; entry.src.powerFault = false; const auto calloutJSON = R"( { "I2C": { "14": { "114": { "Callouts":[ { "Name": "/chassis/motherboard/cpu0", "LocationCode": "P1-C40", "Priority": "H" }, { "Name": "/chassis/motherboard", "LocationCode": "P1", "Priority": "M" }, { "Name": "/chassis/motherboard/bmc", "LocationCode": "P1-C15", "Priority": "L" } ], "Dest": "proc 0 target" } } } })"; auto dataPath = getPELReadOnlyDataPath(); std::ofstream file{dataPath / "systemA_dev_callouts.json"}; file << calloutJSON; file.close(); NiceMock dataIface; std::vector names{"systemA"}; EXPECT_CALL(dataIface, getSystemNames) .Times(5) .WillRepeatedly(Return(names)); EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C40", 0, false)) .Times(3) .WillRepeatedly( Return("/xyz/openbmc_project/inventory/chassis/motherboard/cpu0")); EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false)) .Times(3) .WillRepeatedly( Return("/xyz/openbmc_project/inventory/chassis/motherboard")); EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C15", 0, false)) .Times(3) .WillRepeatedly( Return("/xyz/openbmc_project/inventory/chassis/motherboard/bmc")); EXPECT_CALL(dataIface, expandLocationCode("P1-C40", 0)) .Times(3) .WillRepeatedly(Return("Ufcs-P1-C40")); EXPECT_CALL(dataIface, expandLocationCode("P1", 0)) .Times(3) .WillRepeatedly(Return("Ufcs-P1")); EXPECT_CALL(dataIface, expandLocationCode("P1-C15", 0)) .Times(3) .WillRepeatedly(Return("Ufcs-P1-C15")); EXPECT_CALL( dataIface, getHWCalloutFields( "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _, _)) .Times(3) .WillRepeatedly(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), SetArgReferee<3>("123456789ABC"))); EXPECT_CALL( dataIface, getHWCalloutFields("/xyz/openbmc_project/inventory/chassis/motherboard", _, _, _)) .Times(3) .WillRepeatedly(DoAll(SetArgReferee<1>("7654321"), SetArgReferee<2>("MMMM"), SetArgReferee<3>("CBA987654321"))); EXPECT_CALL( dataIface, getHWCalloutFields( "/xyz/openbmc_project/inventory/chassis/motherboard/bmc", _, _, _)) .Times(3) .WillRepeatedly(DoAll(SetArgReferee<1>("7123456"), SetArgReferee<2>("BBBB"), SetArgReferee<3>("C123456789AB"))); // Call this below with different AdditionalData values that // result in the same callouts. auto checkCallouts = [&entry, &dataIface](const auto& items) { AdditionalData ad{items}; SRC src{entry, ad, dataIface}; ASSERT_TRUE(src.callouts()); auto& callouts = src.callouts()->callouts(); ASSERT_EQ(callouts.size(), 3); { EXPECT_EQ(callouts[0]->priority(), 'H'); EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P1-C40"); auto& fru = callouts[0]->fruIdentity(); EXPECT_EQ(fru->getPN().value(), "1234567"); EXPECT_EQ(fru->getCCIN().value(), "CCCC"); EXPECT_EQ(fru->getSN().value(), "123456789ABC"); } { EXPECT_EQ(callouts[1]->priority(), 'M'); EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P1"); auto& fru = callouts[1]->fruIdentity(); EXPECT_EQ(fru->getPN().value(), "7654321"); EXPECT_EQ(fru->getCCIN().value(), "MMMM"); EXPECT_EQ(fru->getSN().value(), "CBA987654321"); } { EXPECT_EQ(callouts[2]->priority(), 'L'); EXPECT_EQ(callouts[2]->locationCode(), "Ufcs-P1-C15"); auto& fru = callouts[2]->fruIdentity(); EXPECT_EQ(fru->getPN().value(), "7123456"); EXPECT_EQ(fru->getCCIN().value(), "BBBB"); EXPECT_EQ(fru->getSN().value(), "C123456789AB"); } }; { // Callouts based on the device path std::vector items{ "CALLOUT_ERRNO=5", "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/" "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"}; checkCallouts(items); } { // Callouts based on the I2C bus and address std::vector items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=14", "CALLOUT_IIC_ADDR=0x72"}; checkCallouts(items); } { // Also based on I2C bus and address, but with bus = /dev/i2c-14 std::vector items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=14", "CALLOUT_IIC_ADDR=0x72"}; checkCallouts(items); } { // Callout not found std::vector items{ "CALLOUT_ERRNO=5", "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/" "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-24/24-0012"}; AdditionalData ad{items}; SRC src{entry, ad, dataIface}; EXPECT_FALSE(src.callouts()); ASSERT_EQ(src.getDebugData().size(), 1); EXPECT_EQ(src.getDebugData()[0], "Problem looking up I2C callouts on 24 18: " "[json.exception.out_of_range.403] key '24' not found"); } { // Callout not found std::vector items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=22", "CALLOUT_IIC_ADDR=0x99"}; AdditionalData ad{items}; SRC src{entry, ad, dataIface}; EXPECT_FALSE(src.callouts()); ASSERT_EQ(src.getDebugData().size(), 1); EXPECT_EQ(src.getDebugData()[0], "Problem looking up I2C callouts on 22 153: " "[json.exception.out_of_range.403] key '22' not found"); } fs::remove_all(dataPath); } // Test when callouts are passed in via JSON TEST_F(SRCTest, JsonCalloutsTest) { const auto jsonCallouts = R"( [ { "LocationCode": "P0-C1", "Priority": "H", "MRUs": [ { "ID": 42, "Priority": "H" }, { "ID": 43, "Priority": "M" } ] }, { "InventoryPath": "/inv/system/chassis/motherboard/cpu0", "Priority": "M", "Guarded": true, "Deconfigured": true }, { "Procedure": "PROCEDU", "Priority": "A" }, { "SymbolicFRU": "TRUSTED", "Priority": "B", "TrustedLocationCode": true, "LocationCode": "P1-C23" }, { "SymbolicFRU": "FRUTST1", "Priority": "C", "LocationCode": "P1-C24" }, { "SymbolicFRU": "FRUTST2LONG", "Priority": "L" } ] )"_json; message::Entry entry; entry.src.type = 0xBD; entry.src.reasonCode = 0xABCD; entry.subsystem = 0x42; entry.src.powerFault = false; AdditionalData ad; NiceMock dataIface; // Callout 0 mock calls { EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0)) .Times(1) .WillOnce(Return("UXXX-P0-C1")); EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false)) .Times(1) .WillOnce(Return("/inv/system/chassis/motherboard/bmc")); EXPECT_CALL( dataIface, getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _)) .Times(1) .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), SetArgReferee<3>("123456789ABC"))); } // Callout 1 mock calls { EXPECT_CALL(dataIface, getLocationCode("/inv/system/chassis/motherboard/cpu0")) .WillOnce(Return("UYYY-P5")); EXPECT_CALL( dataIface, getHWCalloutFields("/inv/system/chassis/motherboard/cpu0", _, _, _)) .Times(1) .WillOnce(DoAll(SetArgReferee<1>("2345678"), SetArgReferee<2>("DDDD"), SetArgReferee<3>("23456789ABCD"))); } // Callout 3 mock calls { EXPECT_CALL(dataIface, expandLocationCode("P1-C23", 0)) .Times(1) .WillOnce(Return("UXXX-P1-C23")); } // Callout 4 mock calls { EXPECT_CALL(dataIface, expandLocationCode("P1-C24", 0)) .Times(1) .WillOnce(Return("UXXX-P1-C24")); } SRC src{entry, ad, jsonCallouts, dataIface}; ASSERT_TRUE(src.callouts()); // Check the guarded and deconfigured flags EXPECT_TRUE(src.hexwordData()[3] & 0x03000000); const auto& callouts = src.callouts()->callouts(); ASSERT_EQ(callouts.size(), 6); // Check callout 0 { EXPECT_EQ(callouts[0]->priority(), 'H'); EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1"); auto& fru = callouts[0]->fruIdentity(); EXPECT_EQ(fru->getPN().value(), "1234567"); EXPECT_EQ(fru->getCCIN().value(), "CCCC"); EXPECT_EQ(fru->getSN().value(), "123456789ABC"); EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU); auto& mruCallouts = callouts[0]->mru(); ASSERT_TRUE(mruCallouts); auto& mrus = mruCallouts->mrus(); ASSERT_EQ(mrus.size(), 2); EXPECT_EQ(mrus[0].id, 42); EXPECT_EQ(mrus[0].priority, 'H'); EXPECT_EQ(mrus[1].id, 43); EXPECT_EQ(mrus[1].priority, 'M'); } // Check callout 1 { EXPECT_EQ(callouts[1]->priority(), 'M'); EXPECT_EQ(callouts[1]->locationCode(), "UYYY-P5"); auto& fru = callouts[1]->fruIdentity(); EXPECT_EQ(fru->getPN().value(), "2345678"); EXPECT_EQ(fru->getCCIN().value(), "DDDD"); EXPECT_EQ(fru->getSN().value(), "23456789ABCD"); EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU); } // Check callout 2 { EXPECT_EQ(callouts[2]->priority(), 'A'); EXPECT_EQ(callouts[2]->locationCode(), ""); auto& fru = callouts[2]->fruIdentity(); EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU"); EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::maintenanceProc); } // Check callout 3 { EXPECT_EQ(callouts[3]->priority(), 'B'); EXPECT_EQ(callouts[3]->locationCode(), "UXXX-P1-C23"); auto& fru = callouts[3]->fruIdentity(); EXPECT_EQ(fru->getPN().value(), "TRUSTED"); EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRUTrustedLocCode); } // Check callout 4 { EXPECT_EQ(callouts[4]->priority(), 'C'); EXPECT_EQ(callouts[4]->locationCode(), "UXXX-P1-C24"); auto& fru = callouts[4]->fruIdentity(); EXPECT_EQ(fru->getPN().value(), "FRUTST1"); EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU); } // Check callout 5 { EXPECT_EQ(callouts[5]->priority(), 'L'); EXPECT_EQ(callouts[5]->locationCode(), ""); auto& fru = callouts[5]->fruIdentity(); EXPECT_EQ(fru->getPN().value(), "FRUTST2"); EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU); } // Check that it didn't find any errors const auto& data = src.getDebugData(); EXPECT_TRUE(data.empty()); } TEST_F(SRCTest, JsonBadCalloutsTest) { // The first call will have a Throw in a mock call. // The second will have a different Throw in a mock call. // The others have issues with the Priority field. const auto jsonCallouts = R"( [ { "LocationCode": "P0-C1", "Priority": "H" }, { "LocationCode": "P0-C2", "Priority": "H" }, { "LocationCode": "P0-C3" }, { "LocationCode": "P0-C4", "Priority": "X" } ] )"_json; message::Entry entry; entry.src.type = 0xBD; entry.src.reasonCode = 0xABCD; entry.subsystem = 0x42; entry.src.powerFault = false; AdditionalData ad; NiceMock dataIface; // Callout 0 mock calls // Expand location code will fail, so the unexpanded location // code should show up in the callout instead. { EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0)) .WillOnce(Throw(std::runtime_error("Fail"))); EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false)) .Times(1) .WillOnce(Return("/inv/system/chassis/motherboard/bmc")); EXPECT_CALL( dataIface, getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _)) .Times(1) .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), SetArgReferee<3>("123456789ABC"))); } // Callout 1 mock calls // getInventoryFromLocCode will fail { EXPECT_CALL(dataIface, expandLocationCode("P0-C2", 0)) .Times(1) .WillOnce(Return("UXXX-P0-C2")); EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C2", 0, false)) .Times(1) .WillOnce(Throw(std::runtime_error("Fail"))); } SRC src{entry, ad, jsonCallouts, dataIface}; ASSERT_TRUE(src.callouts()); const auto& callouts = src.callouts()->callouts(); // Only the first callout was successful ASSERT_EQ(callouts.size(), 1); { EXPECT_EQ(callouts[0]->priority(), 'H'); EXPECT_EQ(callouts[0]->locationCode(), "P0-C1"); auto& fru = callouts[0]->fruIdentity(); EXPECT_EQ(fru->getPN().value(), "1234567"); EXPECT_EQ(fru->getCCIN().value(), "CCCC"); EXPECT_EQ(fru->getSN().value(), "123456789ABC"); EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU); } const auto& data = src.getDebugData(); ASSERT_EQ(data.size(), 4); EXPECT_STREQ(data[0].c_str(), "Unable to expand location code P0-C1: Fail"); EXPECT_STREQ(data[1].c_str(), "Failed extracting callout data from JSON: Unable to " "get inventory path from location code: P0-C2: Fail"); EXPECT_STREQ(data[2].c_str(), "Failed extracting callout data from JSON: " "[json.exception.out_of_range.403] key 'Priority' not found"); EXPECT_STREQ(data[3].c_str(), "Failed extracting callout data from JSON: Invalid " "priority 'X' found in JSON callout"); } // Test that an inventory path callout can have // a different priority than H. TEST_F(SRCTest, InventoryCalloutTestPriority) { message::Entry entry; entry.src.type = 0xBD; entry.src.reasonCode = 0xABCD; entry.subsystem = 0x42; entry.src.powerFault = false; std::vector adData{"CALLOUT_INVENTORY_PATH=motherboard", "CALLOUT_PRIORITY=M"}; AdditionalData ad{adData}; NiceMock dataIface; EXPECT_CALL(dataIface, getLocationCode("motherboard")) .WillOnce(Return("UTMS-P1")); EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _)) .Times(1) .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), SetArgReferee<3>("123456789ABC"))); SRC src{entry, ad, dataIface}; EXPECT_TRUE(src.valid()); ASSERT_TRUE(src.callouts()); EXPECT_EQ(src.callouts()->callouts().size(), 1); auto& callout = src.callouts()->callouts().front(); EXPECT_EQ(callout->locationCode(), "UTMS-P1"); EXPECT_EQ(callout->priority(), 'M'); }