diff options
Diffstat (limited to 'test/openpower-pels/host_notifier_test.cpp')
-rw-r--r-- | test/openpower-pels/host_notifier_test.cpp | 680 |
1 files changed, 680 insertions, 0 deletions
diff --git a/test/openpower-pels/host_notifier_test.cpp b/test/openpower-pels/host_notifier_test.cpp new file mode 100644 index 0000000..c51d560 --- /dev/null +++ b/test/openpower-pels/host_notifier_test.cpp @@ -0,0 +1,680 @@ +/** + * 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/data_interface.hpp" +#include "extensions/openpower-pels/host_notifier.hpp" +#include "mocks.hpp" +#include "pel_utils.hpp" + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <chrono> + +#include <gtest/gtest.h> + +using namespace openpower::pels; +using ::testing::_; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; +namespace fs = std::filesystem; +using namespace std::chrono; + +const size_t actionFlags0Offset = 66; +const size_t actionFlags1Offset = 67; + +class HostNotifierTest : public CleanPELFiles +{ + public: + HostNotifierTest() : repo(repoPath) + { + auto r = sd_event_default(&event); + EXPECT_TRUE(r >= 0); + + hostIface = + std::make_unique<NiceMock<MockHostInterface>>(event, dataIface); + + mockHostIface = reinterpret_cast<MockHostInterface*>(hostIface.get()); + + auto send = [this](uint32_t id, uint32_t size) { + return this->mockHostIface->send(0); + }; + + // Unless otherwise specified, sendNewLogCmd should always pass. + ON_CALL(*mockHostIface, sendNewLogCmd(_, _)) + .WillByDefault(Invoke(send)); + } + + ~HostNotifierTest() + { + sd_event_unref(event); + } + + protected: + sd_event* event; + Repository repo; + NiceMock<MockDataInterface> dataIface; + std::unique_ptr<HostInterface> hostIface; + MockHostInterface* mockHostIface; +}; + +/** + * @brief Create PEL with the specified action flags + * + * @param[in] actionFlagsMask - Optional action flags to use + * + * @return std::unique_ptr<PEL> + */ +std::unique_ptr<PEL> makePEL(uint16_t actionFlagsMask = 0) +{ + static uint32_t obmcID = 1; + auto data = pelDataFactory(TestPELType::pelSimple); + + data[actionFlags0Offset] |= actionFlagsMask >> 8; + data[actionFlags1Offset] |= actionFlagsMask & 0xFF; + + auto pel = std::make_unique<PEL>(data, obmcID++); + pel->assignID(); + pel->setCommitTime(); + return pel; +} + +/** + * @brief Run an iteration of the event loop. + * + * An event loop is used for: + * 1) timer expiration callbacks + * 2) Dispatches + * 3) host interface receive callbacks + * + * @param[in] event - The event object + * @param[in] numEvents - number of times to call Event::run() + * @param[in] timeout - timeout value for run() + */ +void runEvents(sdeventplus::Event& event, size_t numEvents, + milliseconds timeout = milliseconds(1)) +{ + for (size_t i = 0; i < numEvents; i++) + { + event.run(timeout); + } +} + +// Test that host state change callbacks work +TEST_F(HostNotifierTest, TestHostStateChange) +{ + bool hostState = false; + bool called = false; + DataInterfaceBase::HostStateChangeFunc func = [&hostState, + &called](bool state) { + hostState = state; + called = true; + }; + + dataIface.subscribeToHostStateChange("test", func); + + // callback called + dataIface.changeHostState(true); + EXPECT_TRUE(called); + EXPECT_TRUE(hostState); + + // No change, not called + called = false; + dataIface.changeHostState(true); + EXPECT_FALSE(called); + + // Called again + dataIface.changeHostState(false); + EXPECT_FALSE(hostState); + EXPECT_TRUE(called); + + // Shouldn't get called after an unsubscribe + dataIface.unsubscribeFromHostStateChange("test"); + + called = false; + + dataIface.changeHostState(true); + EXPECT_FALSE(called); +} + +// Test dealing with how acked PELs are put on the +// notification queue. +TEST_F(HostNotifierTest, TestPolicyAckedPEL) +{ + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + auto pel = makePEL(); + repo.add(pel); + + // This is required + EXPECT_TRUE(notifier.enqueueRequired(pel->id())); + EXPECT_TRUE(notifier.notifyRequired(pel->id())); + + // Not in the repo + EXPECT_FALSE(notifier.enqueueRequired(42)); + EXPECT_FALSE(notifier.notifyRequired(42)); + + // Now set this PEL to host acked + repo.setPELHostTransState(pel->id(), TransmissionState::acked); + + // Since it's acked, doesn't need to be enqueued or transmitted + EXPECT_FALSE(notifier.enqueueRequired(pel->id())); + EXPECT_FALSE(notifier.notifyRequired(pel->id())); +} + +// Test the 'don't report' PEL flag +TEST_F(HostNotifierTest, TestPolicyDontReport) +{ + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + // dontReportToHostFlagBit + auto pel = makePEL(0x1000); + + // Double check the action flag is still set + std::bitset<16> actionFlags = pel->userHeader().actionFlags(); + EXPECT_TRUE(actionFlags.test(dontReportToHostFlagBit)); + + repo.add(pel); + + // Don't need to send this to the host + EXPECT_FALSE(notifier.enqueueRequired(pel->id())); +} + +// Test that hidden PELs need notification when there +// is no HMC. +TEST_F(HostNotifierTest, TestPolicyHiddenNoHMC) +{ + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + // hiddenFlagBit + auto pel = makePEL(0x4000); + + // Double check the action flag is still set + std::bitset<16> actionFlags = pel->userHeader().actionFlags(); + EXPECT_TRUE(actionFlags.test(hiddenFlagBit)); + + repo.add(pel); + + // Still need to enqueue this + EXPECT_TRUE(notifier.enqueueRequired(pel->id())); + + // Still need to send it + EXPECT_TRUE(notifier.notifyRequired(pel->id())); +} + +// Don't need to enqueue a hidden log already acked by the HMC +TEST_F(HostNotifierTest, TestPolicyHiddenWithHMCAcked) +{ + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + // hiddenFlagBit + auto pel = makePEL(0x4000); + + // Double check the action flag is still set + std::bitset<16> actionFlags = pel->userHeader().actionFlags(); + EXPECT_TRUE(actionFlags.test(hiddenFlagBit)); + + repo.add(pel); + + // No HMC yet, so required + EXPECT_TRUE(notifier.enqueueRequired(pel->id())); + + repo.setPELHMCTransState(pel->id(), TransmissionState::acked); + + // Not required anymore + EXPECT_FALSE(notifier.enqueueRequired(pel->id())); +} + +// Test that changing the HMC manage status affects +// the policy with hidden log notification. +TEST_F(HostNotifierTest, TestPolicyHiddenWithHMCManaged) +{ + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + // hiddenFlagBit + auto pel = makePEL(0x4000); + + repo.add(pel); + + // The first time, the HMC managed is false + EXPECT_TRUE(notifier.notifyRequired(pel->id())); + + dataIface.setHMCManaged(true); + + // This time, HMC managed is true so no need to notify + EXPECT_FALSE(notifier.notifyRequired(pel->id())); +} + +// Test that PELs are enqueued on startup +TEST_F(HostNotifierTest, TestStartup) +{ + // Give the repo 10 PELs to start with + for (int i = 0; i < 10; i++) + { + auto pel = makePEL(); + repo.add(pel); + } + + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + ASSERT_EQ(notifier.queueSize(), 10); + + // Now add 10 more after the notifier is watching + for (int i = 0; i < 10; i++) + { + auto pel = makePEL(); + repo.add(pel); + } + + ASSERT_EQ(notifier.queueSize(), 20); +} + +// Test the simple path were PELs get sent to the host +TEST_F(HostNotifierTest, TestSendCmd) +{ + sdeventplus::Event sdEvent{event}; + + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + // Add a PEL with the host off + auto pel = makePEL(); + repo.add(pel); + + EXPECT_EQ(notifier.queueSize(), 1); + + dataIface.changeHostState(true); + + runEvents(sdEvent, 1); + + // It was sent up + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1); + EXPECT_EQ(notifier.queueSize(), 0); + + // Verify the state was written to the PEL. + Repository::LogID id{Repository::LogID::Pel{pel->id()}}; + auto data = repo.getPELData(id); + PEL pelFromRepo{*data}; + EXPECT_EQ(pelFromRepo.hostTransmissionState(), TransmissionState::sent); + + // Add a few more PELs. They will get sent. + pel = makePEL(); + repo.add(pel); + + // Dispatch it by hitting the event loop (no commands sent yet) + // Don't need to test this step discretely in the future + runEvents(sdEvent, 1); + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1); + EXPECT_EQ(notifier.queueSize(), 0); + + // Send the command + runEvents(sdEvent, 1); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 2); + EXPECT_EQ(notifier.queueSize(), 0); + + pel = makePEL(); + repo.add(pel); + + // dispatch and process the command + runEvents(sdEvent, 2); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 3); + EXPECT_EQ(notifier.queueSize(), 0); +} + +// Test that if the class is created with the host up, +// it will send PELs +TEST_F(HostNotifierTest, TestStartAfterHostUp) +{ + // Add PELs right away + auto pel = makePEL(); + repo.add(pel); + pel = makePEL(); + repo.add(pel); + + sdeventplus::Event sdEvent{event}; + + // Create the HostNotifier class with the host already up + dataIface.changeHostState(true); + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + // It should start sending PELs right away + runEvents(sdEvent, 2); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 2); + EXPECT_EQ(notifier.queueSize(), 0); +} + +// Test that a single failure will cause a retry +TEST_F(HostNotifierTest, TestHostRetry) +{ + sdeventplus::Event sdEvent{event}; + + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + auto sendFailure = [this](uint32_t id, uint32_t size) { + return this->mockHostIface->send(1); + }; + auto sendSuccess = [this](uint32_t id, uint32_t size) { + return this->mockHostIface->send(0); + }; + + EXPECT_CALL(*mockHostIface, sendNewLogCmd(_, _)) + .WillOnce(Invoke(sendFailure)) + .WillOnce(Invoke(sendSuccess)) + .WillOnce(Invoke(sendSuccess)); + + dataIface.changeHostState(true); + + auto pel = makePEL(); + repo.add(pel); + + // Dispatch and handle the command + runEvents(sdEvent, 2); + + // The command failed, so the queue isn't empty + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1); + EXPECT_EQ(notifier.queueSize(), 1); + + // Run the events again to let the timer expire and the + // command to be retried, which will be successful. + runEvents(sdEvent, 2, mockHostIface->getReceiveRetryDelay()); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 2); + EXPECT_EQ(notifier.queueSize(), 0); + + // This one should pass with no problems + pel = makePEL(); + repo.add(pel); + + // Dispatch and handle the command + runEvents(sdEvent, 2); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 3); + EXPECT_EQ(notifier.queueSize(), 0); +} + +// Test that all commands fail and notifier will give up +TEST_F(HostNotifierTest, TestHardFailure) +{ + sdeventplus::Event sdEvent{event}; + + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + // Every call will fail + auto sendFailure = [this](uint32_t id, uint32_t size) { + return this->mockHostIface->send(1); + }; + + EXPECT_CALL(*mockHostIface, sendNewLogCmd(_, _)) + .WillRepeatedly(Invoke(sendFailure)); + + dataIface.changeHostState(true); + + auto pel = makePEL(); + repo.add(pel); + + // Clock more retries than necessary + runEvents(sdEvent, 40, mockHostIface->getReceiveRetryDelay()); + + // Should have stopped after the 15 Tries + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 15); + EXPECT_EQ(notifier.queueSize(), 1); + + // Now add another PEL, and it should start trying again + // though it will also eventually give up + pel = makePEL(); + repo.add(pel); + + runEvents(sdEvent, 40, mockHostIface->getReceiveRetryDelay()); + + // Tried an additional 15 times + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 30); + EXPECT_EQ(notifier.queueSize(), 2); +} + +// Cancel an in progress command +TEST_F(HostNotifierTest, TestCancelCmd) +{ + sdeventplus::Event sdEvent{event}; + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + dataIface.changeHostState(true); + + // Add and send one PEL, but don't enter the event loop + // so the receive function can't run. + auto pel = makePEL(); + repo.add(pel); + + // Not dispatched yet + EXPECT_EQ(notifier.queueSize(), 1); + + // Dispatch it + runEvents(sdEvent, 1); + + // It was sent and off the queue + EXPECT_EQ(notifier.queueSize(), 0); + + // This will cancel the receive + dataIface.changeHostState(false); + + // Back on the queue + EXPECT_EQ(notifier.queueSize(), 1); + + // Turn the host back on and make sure + // commands will work again + dataIface.changeHostState(true); + + runEvents(sdEvent, 1); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1); + EXPECT_EQ(notifier.queueSize(), 0); +} + +// Test that acking a PEL persist across power cycles +TEST_F(HostNotifierTest, TestPowerCycleAndAcks) +{ + sdeventplus::Event sdEvent{event}; + + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + // Add 2 PELs with host off + auto pel = makePEL(); + repo.add(pel); + auto id1 = pel->id(); + + pel = makePEL(); + repo.add(pel); + auto id2 = pel->id(); + + dataIface.changeHostState(true); + + runEvents(sdEvent, 2); + + // The were both sent. + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 2); + EXPECT_EQ(notifier.queueSize(), 0); + + dataIface.changeHostState(false); + + // Those PELs weren't acked, so they will get sent again + EXPECT_EQ(notifier.queueSize(), 2); + + // Power back on and send them again + dataIface.changeHostState(true); + runEvents(sdEvent, 2); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 4); + EXPECT_EQ(notifier.queueSize(), 0); + + // Ack them and verify the state in the PEL. + notifier.ackPEL(id1); + notifier.ackPEL(id2); + + Repository::LogID id{Repository::LogID::Pel{id1}}; + auto data = repo.getPELData(id); + PEL pelFromRepo1{*data}; + EXPECT_EQ(pelFromRepo1.hostTransmissionState(), TransmissionState::acked); + + id.pelID.id = id2; + data = repo.getPELData(id); + PEL pelFromRepo2{*data}; + EXPECT_EQ(pelFromRepo2.hostTransmissionState(), TransmissionState::acked); + + // Power back off, and they should't get re-added + dataIface.changeHostState(false); + + EXPECT_EQ(notifier.queueSize(), 0); +} + +// Test the host full condition +TEST_F(HostNotifierTest, TestHostFull) +{ + // The full interaction with the host is: + // BMC: new PEL available + // Host: ReadPELFile (not modeled here) + // Host: Ack(id) (if not full), or HostFull(id) + // BMC: if full and any new PELs come in, don't sent them + // Start a timer and try again + // Host responds with either Ack or full + // and repeat + + sdeventplus::Event sdEvent{event}; + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + dataIface.changeHostState(true); + + // Add and dispatch/send one PEL + auto pel = makePEL(); + auto id = pel->id(); + repo.add(pel); + runEvents(sdEvent, 2); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1); + EXPECT_EQ(notifier.queueSize(), 0); + + // Host is full + notifier.setHostFull(id); + + // It goes back on the queue + EXPECT_EQ(notifier.queueSize(), 1); + + // The transmission state goes back to new + Repository::LogID i{Repository::LogID::Pel{id}}; + auto data = repo.getPELData(i); + PEL pelFromRepo{*data}; + EXPECT_EQ(pelFromRepo.hostTransmissionState(), TransmissionState::newPEL); + + // Clock it, nothing should be sent still. + runEvents(sdEvent, 1); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1); + EXPECT_EQ(notifier.queueSize(), 1); + + // Add another PEL and clock it, still nothing sent + pel = makePEL(); + repo.add(pel); + runEvents(sdEvent, 2); + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1); + EXPECT_EQ(notifier.queueSize(), 2); + + // Let the host full timer expire to trigger a retry. + // Add some extra event passes just to be sure nothing new is sent. + runEvents(sdEvent, 5, mockHostIface->getHostFullRetryDelay()); + + // The timer expiration will send just the 1, not both + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 2); + EXPECT_EQ(notifier.queueSize(), 1); + + // Host still full + notifier.setHostFull(id); + + // Let the host full timer attempt again + runEvents(sdEvent, 2, mockHostIface->getHostFullRetryDelay()); + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 3); + + // Add yet another PEL with the retry timer expired. + // It shouldn't get sent out. + pel = makePEL(); + repo.add(pel); + runEvents(sdEvent, 2); + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 3); + + // The last 2 PELs still on the queue + EXPECT_EQ(notifier.queueSize(), 2); + + // Host no longer full, it finally acks the first PEL + notifier.ackPEL(id); + + // Now the remaining 2 PELs will be dispatched + runEvents(sdEvent, 3); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 5); + EXPECT_EQ(notifier.queueSize(), 0); +} + +// Test when the host says it was send a malformed PEL +TEST_F(HostNotifierTest, TestBadPEL) +{ + sdeventplus::Event sdEvent{event}; + + { + Repository repo1{repoPath}; + HostNotifier notifier{repo1, dataIface, std::move(hostIface)}; + + dataIface.changeHostState(true); + + // Add a PEL and dispatch and send it + auto pel = makePEL(); + auto id = pel->id(); + repo1.add(pel); + + runEvents(sdEvent, 2); + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1); + EXPECT_EQ(notifier.queueSize(), 0); + + // The host rejected it. + notifier.setBadPEL(id); + + // Doesn't go back on the queue + EXPECT_EQ(notifier.queueSize(), 0); + + // Check the state was saved in the PEL itself + Repository::LogID i{Repository::LogID::Pel{id}}; + auto data = repo1.getPELData(i); + PEL pelFromRepo{*data}; + EXPECT_EQ(pelFromRepo.hostTransmissionState(), + TransmissionState::badPEL); + + dataIface.changeHostState(false); + + // Ensure it doesn't go back on the queue on a power cycle + EXPECT_EQ(notifier.queueSize(), 0); + } + + // Now restore the repo, and make sure it doesn't come back + { + Repository repo1{repoPath}; + + std::unique_ptr<HostInterface> hostIface1 = + std::make_unique<MockHostInterface>(event, dataIface); + + HostNotifier notifier{repo1, dataIface, std::move(hostIface1)}; + + EXPECT_EQ(notifier.queueSize(), 0); + } +} |