blob: 18d1f60a582556e212601414ee5245cc59fad20f [file] [log] [blame]
#include "dbus_utility.hpp"
#include "managed_store.hpp"
#include "managed_store_types.hpp"
#include <boost/asio/io_context.hpp>
#include <memory>
#include <thread>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace managedStore
{
namespace
{
using namespace std::chrono_literals;
using testing::_;
using testing::DoAll;
using testing::InvokeArgument;
using testing::Return;
constexpr std::chrono::duration kTestShortDuration = 20ms;
constexpr std::chrono::duration kTestMediumDuration = 50ms;
constexpr std::chrono::duration kTestLongDuration = 100ms;
// Pending dbus response count used in tests.
constexpr size_t kPendingDbusResponseLow = 2;
constexpr size_t kPendingDbusResponseHigh = 10;
class MockManagedObjectStore : public ManagedObjectStore
{
public:
MockManagedObjectStore(const ManagedObjectStoreConfig& cfg,
boost::asio::io_context& io) :
ManagedObjectStore(cfg, io)
{}
const std::unordered_map<std::string, ValueType>&
getManagedObjectsFromStore() const
{
return this->getManagedObjects();
}
const ManagedStorePriorityQueue& getManagedStorePriorityQueue() const
{
return this->getPriorityQueue();
}
MOCK_METHOD(void, getManagedObjectsFromDbusService,
(const std::string&, const sdbusplus::message::object_path&,
ManagedStoreCb&&),
(override));
MOCK_METHOD(void, getManagedPropertiesMapFromDbusService,
(const std::string&, const sdbusplus::message::object_path&,
const std::string&, ManagedStoreCb&&),
(override));
};
ValueType GenerateTestValue(
ManagedType type = ManagedType::kManagedObject,
std::chrono::milliseconds tfixedTestDuration = kTestShortDuration)
{
const auto now = clockNow();
if (type == ManagedType::kManagedPropertyMap)
{
return ValueType(
dbus::utility::DBusPropertiesMap{},
boost::system::errc::make_error_code(boost::system::errc::success),
now, now + tfixedTestDuration);
}
return ValueType(
dbus::utility::ManagedObjectType{},
boost::system::errc::make_error_code(boost::system::errc::success), now,
now + tfixedTestDuration);
}
TEST(ManagedStoreTest, TestPendingDbusCallsDoNotExceedThreshold)
{
auto io = std::make_shared<boost::asio::io_context>();
managedStore::ManagedObjectStoreConfig config(
true /*isEnabled*/, kPendingDbusResponseLow /*pendingDbusResponsesMax*/,
kTestShortDuration /*tfixed*/, kTestShortDuration /*tgrace*/,
kTestLongDuration /*tLRU*/);
auto mockManagedStore =
std::make_shared<MockManagedObjectStore>(config, *io);
auto testVal = GenerateTestValue();
EXPECT_CALL(*mockManagedStore, getManagedObjectsFromDbusService(_, _, _))
.Times(5) // 3 High priority + 2 low priority pending dbus calls allowed
.WillOnce(
::testing::InvokeArgument<2>(testVal)) // High priority, upsert!
.WillOnce(
::testing::InvokeArgument<2>(testVal)) // High priority, upsert!
.WillOnce(
::testing::InvokeArgument<2>(testVal)) // High priority, upsert!
.WillOnce(Return()) // No response sent to ensure pending state
.WillOnce(Return()); // No response sent to ensure pending state
mockManagedStore->getManagedObjects(
"xyz", {},
[](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
mockManagedStore->getManagedObjects(
"abc", {},
[](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
mockManagedStore->getManagedObjects(
"efg", {},
[](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
io->run_for(kTestShortDuration);
EXPECT_EQ(mockManagedStore->getStoreTracker().countPendingDbusResponses, 2);
}
TEST(ManagedStoreTest, TestHighPriorityDbusCallsNotBlocked)
{
auto io = std::make_shared<boost::asio::io_context>();
managedStore::ManagedObjectStoreConfig config(
true /*isEnabled*/,
kPendingDbusResponseLow /*pendingDbusResponsesMax*/);
auto mockManagedStore =
std::make_shared<MockManagedObjectStore>(config, *io);
int executionCount = 5;
EXPECT_CALL(*mockManagedStore, getManagedObjectsFromDbusService).Times(1);
for (int i = 0; i < executionCount; ++i)
{
mockManagedStore->getManagedObjects(
"", {},
[](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
}
}
TEST(ManagedStoreTest, TestGetManagedObjectCallbackInvoked)
{
auto io = std::make_shared<boost::asio::io_context>();
managedStore::ManagedObjectStoreConfig config(
true /*isEnabled*/,
kPendingDbusResponseLow /*pendingDbusResponsesMax*/);
auto mockManagedStore =
std::make_shared<MockManagedObjectStore>(config, *io);
size_t callbackInvoked = 0;
EXPECT_CALL(*mockManagedStore, getManagedObjectsFromDbusService(_, _, _))
.WillOnce(::testing::InvokeArgument<2>(
GenerateTestValue())); // High priority, upsert!
mockManagedStore->getManagedObjects(
"", {},
[&callbackInvoked](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {
++callbackInvoked;
});
EXPECT_EQ(callbackInvoked, 1);
}
TEST(ManagedStoreTest, TestManagedStoreRefreshesObjectsInStoreAfterTfixed)
{
// Setup
auto io = std::make_shared<boost::asio::io_context>();
managedStore::ManagedObjectStoreConfig config(
true /*isEnabled*/,
kPendingDbusResponseHigh /*pendingDbusResponsesMax*/,
kTestMediumDuration /*tfixed*/, kTestMediumDuration /*tgrace*/);
auto mockManagedStore =
std::make_shared<MockManagedObjectStore>(config, *io);
const auto& objects = mockManagedStore->getManagedObjectsFromStore();
// Configure mock to repeatedly respond to refresh requests.
EXPECT_CALL(*mockManagedStore, getManagedObjectsFromDbusService(_, _, _))
.WillRepeatedly(::testing::InvokeArgument<2>(GenerateTestValue(
ManagedType::kManagedObject, kTestMediumDuration)));
// First call for each object adds an entry in the store.
mockManagedStore->getManagedObjects(
"xyz", {},
[](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
mockManagedStore->getManagedObjects(
"abc", {},
[](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
mockManagedStore->getManagedObjects(
"efg", {},
[](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
// Run scheduler for time less than tfixed to make sure no refreshes
io->run_for(kTestShortDuration);
// No refresh until scheduled time
for (const auto& iter : objects)
{
EXPECT_EQ(0, iter.second.numRefreshesDone);
}
// Now run the scheduler to make sure time just goes beyond tfixed and
// refresh occurs.
io->run_for(kTestMediumDuration);
// Refresh occurs when time > tfixed.
for (const auto& iter : objects)
{
EXPECT_GT(iter.second.numRefreshesDone, 1);
EXPECT_EQ(1, iter.second.numTimesUsed);
}
auto tracker = mockManagedStore->getStoreTracker();
EXPECT_EQ(3, tracker.countGetManagedObjectsCacheMiss);
EXPECT_EQ(0, tracker.countGetManagedObjectsCacheHit);
EXPECT_EQ(0, tracker.countGetManagedObjectsCacheMissMaxAge);
// second call:
mockManagedStore->getManagedObjects(
"xyz", {},
[](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
mockManagedStore->getManagedObjects(
"abc", {},
[](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
mockManagedStore->getManagedObjects(
"efg", {},
[](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
// numTimesUsed is now at 2
for (const auto& iter : mockManagedStore->getManagedObjectsFromStore())
{
EXPECT_EQ(2, iter.second.numTimesUsed);
EXPECT_GT(iter.second.lastUsedAges->size(), 0);
}
tracker = mockManagedStore->getStoreTracker();
EXPECT_EQ(3, tracker.countGetManagedObjectsCacheMiss);
EXPECT_EQ(3, tracker.countGetManagedObjectsCacheHit);
EXPECT_EQ(0, tracker.countGetManagedObjectsCacheMissMaxAge);
}
TEST(ManagedStoreTest, TestObjectsInStoreAreEvictedAfterLruThreshold)
{
// Setup
auto io = std::make_shared<boost::asio::io_context>();
managedStore::ManagedObjectStoreConfig config(
true /*isEnabled*/,
kPendingDbusResponseHigh /*pendingDbusResponsesMax*/,
std::chrono::milliseconds(kTestShortDuration) /*tfixed*/,
std::chrono::milliseconds(kTestShortDuration) /*tgrace*/,
std::chrono::milliseconds(kTestMediumDuration) /*tLRU*/);
auto mockManagedStore =
std::make_shared<MockManagedObjectStore>(config, *io);
const auto& objects = mockManagedStore->getManagedObjectsFromStore();
// Configure mock to repeatedly respond to refresh requests.
EXPECT_CALL(*mockManagedStore, getManagedObjectsFromDbusService(_, _, _))
.WillRepeatedly(::testing::InvokeArgument<2>(GenerateTestValue()));
// First call for each object adds an entry in the store.
mockManagedStore->getManagedObjects(
"xyz", {},
[](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
// Run scheduler till the first refresh is due
// At this point the object is still fresh.
io->run_for(kTestShortDuration);
// We expect the objects will be in store.
EXPECT_EQ(1, objects.size());
// Now run the scheduler to make sure time exceeds LRU threshold
io->run_for(kTestMediumDuration);
// We expect the object to be evicted.
EXPECT_EQ(0, objects.size());
}
TEST(ManagedStoreTest, TestManagedStoreRefreshesOverdueObjects)
{
auto io = std::make_shared<boost::asio::io_context>();
managedStore::ManagedObjectStoreConfig config(
true /*isEnabled*/, 5 /*thresholdPendingDbusResponses*/,
kTestShortDuration /*tfixed*/, kTestShortDuration /*tgrace*/,
kTestLongDuration /*tLRU*/);
auto mockManagedStore =
std::make_shared<MockManagedObjectStore>(config, *io);
auto testVal = GenerateTestValue();
managedStore::ManagedObjectStore::ManagedStoreCb managedStoreCb;
EXPECT_CALL(*mockManagedStore, getManagedObjectsFromDbusService(_, _, _))
.Times(6) // 3 cache miss + 3 refresh call
.WillOnce(
::testing::InvokeArgument<2>(testVal)) // High priority, upsert!
.WillOnce(
::testing::InvokeArgument<2>(testVal)) // High priority, upsert!
.WillOnce(
::testing::InvokeArgument<2>(testVal)) // High priority, upsert!
.WillOnce(Return()) // Refresh
.WillOnce(Return()) // Refresh
.WillOnce(Return()); // Refresh
mockManagedStore->getManagedObjects(
"xyz", {},
[](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
mockManagedStore->getManagedObjects(
"abc", {},
[](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
mockManagedStore->getManagedObjects(
"efg", {},
[](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
// At this point the Cache is warm with 3 ManagedObjects.
// One object from the queue which has the earliest deadline will be pop'd
// to schedule a refresh after tfixed. So priority queue will only have 2
// elements.
EXPECT_EQ(2, mockManagedStore->getManagedStorePriorityQueue().size());
// If we run io event loop now, all overdue objects should refresh and
// priority queue should get empty.
io->run_for(kTestShortDuration);
// Check there are no objects in PQ
EXPECT_EQ(0, mockManagedStore->getManagedStorePriorityQueue().size());
}
TEST(ManagedStoreTest, TestManagedStorePreventsDuplicateReadThrough)
{
auto io = std::make_shared<boost::asio::io_context>();
managedStore::ManagedObjectStoreConfig config(
true /*isEnabled*/, 5 /*thresholdPendingDbusResponses*/,
kTestShortDuration /*tfixed*/, kTestShortDuration /*tgrace*/,
kTestLongDuration /*tLRU*/);
auto mockManagedStore =
std::make_shared<MockManagedObjectStore>(config, *io);
auto testVal = GenerateTestValue();
EXPECT_CALL(*mockManagedStore, getManagedObjectsFromDbusService(_, _, _))
.Times(2)
.WillRepeatedly(
::testing::InvokeArgument<2>(testVal)); // High priority, upsert!
size_t callbackInvoked = 0;
mockManagedStore->getManagedObjects(
"abc", {},
[&](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) { ++callbackInvoked; });
// 2 Similar requests back to back to create the test scenario where
// managed store will have to block redundant request.
mockManagedStore->getManagedObjects(
"xyz", {},
[&](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) { ++callbackInvoked; });
mockManagedStore->getManagedObjects(
"xyz", {},
[&](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) { ++callbackInvoked; });
// abc is scheduled and 1 xyz in priority queue waiting to be scheduled
// Note, we don't expect 2 xyz in priority queue since the store does not
// allow multiple read through requests.
EXPECT_EQ(1, mockManagedStore->getManagedStorePriorityQueue().size());
// Make sure the callback is invoked for duplicate xyz request.
EXPECT_EQ(callbackInvoked, 3);
}
TEST(ManagedStoreTest, TestGetAllProperties)
{
auto io = std::make_shared<boost::asio::io_context>();
managedStore::ManagedObjectStoreConfig config(
true /*isEnabled*/,
kPendingDbusResponseLow /*thresholdPendingDbusResponses*/);
auto mockManagedStore =
std::make_shared<MockManagedObjectStore>(config, *io);
size_t callbackInvoked = 0;
EXPECT_CALL(*mockManagedStore,
getManagedPropertiesMapFromDbusService(_, _, _, _))
.Times(1)
.WillRepeatedly(::testing::InvokeArgument<3>(GenerateTestValue(
ManagedType::kManagedPropertyMap))); // High priority, upsert!
std::shared_ptr<bmcweb::AsyncResp> asyncResp =
std::make_shared<bmcweb::AsyncResp>();
managedStore::ManagedObjectStoreContext context(asyncResp);
mockManagedStore->getAllProperties(
"ObjectPath", "Interface", "Property", context,
[&callbackInvoked](const boost::system::error_code&,
const dbus::utility::DBusPropertiesMap&) {
++callbackInvoked;
});
EXPECT_EQ(callbackInvoked, 1);
// At this point GetAllProperty should be stored and any further requests
// should be served from store.
mockManagedStore->getAllProperties(
"ObjectPath", "Interface", "Property", context,
[&callbackInvoked](const boost::system::error_code&,
const dbus::utility::DBusPropertiesMap&) {
++callbackInvoked;
});
EXPECT_EQ(callbackInvoked, 2);
}
TEST(ManagedStoreTest, TestManagedStoreAvoidsDuplicatePqEntries)
{
auto io = std::make_shared<boost::asio::io_context>();
managedStore::ManagedObjectStoreConfig config(
true /*isEnabled*/, 1 /*thresholdPendingDbusResponses*/,
kTestShortDuration /*tfixed*/, 0ms /*tgrace*/,
kTestLongDuration /*tLRU*/);
auto mockManagedStore =
std::make_shared<MockManagedObjectStore>(config, *io);
{
testing::InSequence s;
EXPECT_CALL(*mockManagedStore,
getManagedObjectsFromDbusService(_, _, _))
.WillOnce(::testing::InvokeArgument<2>(GenerateTestValue()));
EXPECT_CALL(*mockManagedStore,
getManagedObjectsFromDbusService(_, _, _))
.WillOnce(::testing::InvokeArgument<2>(GenerateTestValue()));
EXPECT_CALL(*mockManagedStore,
getManagedObjectsFromDbusService(_, _, _))
.WillOnce(::testing::InvokeArgument<2>(GenerateTestValue()));
EXPECT_CALL(*mockManagedStore,
getManagedObjectsFromDbusService(_, _, _))
.WillOnce(::testing::InvokeArgument<2>(GenerateTestValue()));
EXPECT_CALL(*mockManagedStore,
getManagedObjectsFromDbusService(_, _, _))
.WillOnce(::testing::InvokeArgument<2>(GenerateTestValue()));
}
mockManagedStore->getManagedObjects(
"abc", {},
[&](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
// Expect PQ size to be 0 since the store has only 1 entry at this point
// which will always be scheduled for refresh.
EXPECT_EQ(0, mockManagedStore->getManagedStorePriorityQueue().size());
// Add another entry. This time the entry should wait in PQ to be scheduled.
mockManagedStore->getManagedObjects(
"xyz", {},
[&](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
EXPECT_EQ(1, mockManagedStore->getManagedStorePriorityQueue().size());
// Make the entries in the PQ stale
std::this_thread::sleep_for(kTestShortDuration);
mockManagedStore->getManagedObjects(
"def", {},
[&](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
EXPECT_EQ(2, mockManagedStore->getManagedStorePriorityQueue().size());
// Now send a request for object that has become stale in managedStore.
// We want to make sure pq size does not grow and the old pq entry for xyz
// is pop'd and gets requeued after "def"
mockManagedStore->getManagedObjects(
"xyz", {},
[&](const boost::system::error_code&,
const dbus::utility::ManagedObjectType&) {});
// Expect PQ size to sill be 2 as managed store makes sure duplicate entries
// don't go into the priority queue.
EXPECT_EQ(2, mockManagedStore->getManagedStorePriorityQueue().size());
// Run io loop once to trigger scheduler to fetch new entries from pq.
io->run_one();
auto pq = mockManagedStore->getManagedStorePriorityQueue();
// We want to make sure that the old pq entry for xyz is pop'd and gets
// requeued after "def". "def" will be picked by the scheduler in that case
// and we should see "xyz" as the top entry in pq (which in error case
// would have been "abc")
EXPECT_EQ("0|xyz|", pq.top().managedStoreKey.toString());
}
} // namespace
} // namespace managedStore