blob: 3e5818aa8e5111df75d923a71b1f0d703316cabf [file] [log] [blame]
#include "dbus_environment.hpp"
#include "helpers.hpp"
#include "messages/collect_trigger_id.hpp"
#include "messages/trigger_presence_changed_ind.hpp"
#include "mocks/json_storage_mock.hpp"
#include "mocks/report_manager_mock.hpp"
#include "mocks/sensor_mock.hpp"
#include "mocks/threshold_mock.hpp"
#include "mocks/trigger_factory_mock.hpp"
#include "mocks/trigger_manager_mock.hpp"
#include "params/trigger_params.hpp"
#include "trigger.hpp"
#include "trigger_manager.hpp"
#include "utils/conversion_trigger.hpp"
#include "utils/dbus_path_utils.hpp"
#include "utils/messanger.hpp"
#include "utils/string_utils.hpp"
#include "utils/transform.hpp"
#include "utils/tstring.hpp"
#include <boost/range/combine.hpp>
using namespace testing;
using namespace std::literals::string_literals;
using sdbusplus::message::object_path;
static constexpr size_t expectedTriggerVersion = 2;
class TestTrigger : public Test
{
public:
TriggerParams triggerParams;
TriggerParams triggerDiscreteParams =
TriggerParams()
.id("DiscreteTrigger")
.name("My Discrete Trigger")
.thresholdParams(std::vector<discrete::LabeledThresholdParam>{
discrete::LabeledThresholdParam{
"userId", discrete::Severity::warning,
Milliseconds(10).count(), "15.2"},
discrete::LabeledThresholdParam{
"userId_2", discrete::Severity::critical,
Milliseconds(5).count(), "32.7"},
});
std::unique_ptr<ReportManagerMock> reportManagerMockPtr =
std::make_unique<NiceMock<ReportManagerMock>>();
std::unique_ptr<TriggerManagerMock> triggerManagerMockPtr =
std::make_unique<NiceMock<TriggerManagerMock>>();
std::unique_ptr<TriggerFactoryMock> triggerFactoryMockPtr =
std::make_unique<NiceMock<TriggerFactoryMock>>();
testing::NiceMock<StorageMock> storageMock;
NiceMock<MockFunction<void(const messages::TriggerPresenceChangedInd)>>
triggerPresenceChanged;
std::vector<std::shared_ptr<interfaces::Threshold>> thresholdMocks;
utils::Messanger messanger;
std::unique_ptr<Trigger> sut;
TestTrigger() : messanger(DbusEnvironment::getIoc())
{
messanger.on_receive<messages::TriggerPresenceChangedInd>(
[this](const auto& msg) { triggerPresenceChanged.Call(msg); });
}
void SetUp() override
{
sut = makeTrigger(triggerParams);
}
static std::vector<LabeledSensorInfo>
convertToLabeledSensor(const SensorsInfo& sensorsInfo)
{
return utils::transform(sensorsInfo, [](const auto& sensorInfo) {
const auto& [sensorPath, sensorMetadata] = sensorInfo;
return LabeledSensorInfo("service1", sensorPath, sensorMetadata);
});
}
std::unique_ptr<Trigger> makeTrigger(const TriggerParams& params)
{
thresholdMocks =
ThresholdMock::makeThresholds(params.thresholdParams());
auto id = std::make_unique<const std::string>(params.id());
return std::make_unique<Trigger>(
DbusEnvironment::getIoc(), DbusEnvironment::getObjServer(),
std::move(id), params.name(), params.triggerActions(),
std::make_shared<std::vector<std::string>>(
params.reportIds().begin(), params.reportIds().end()),
std::vector<std::shared_ptr<interfaces::Threshold>>(thresholdMocks),
*triggerManagerMockPtr, storageMock, *triggerFactoryMockPtr,
SensorMock::makeSensorMocks(params.sensors()));
}
static interfaces::JsonStorage::FilePath to_file_path(std::string name)
{
return interfaces::JsonStorage::FilePath(
std::to_string(std::hash<std::string>{}(name)));
}
template <class T>
static T getProperty(const std::string& path, const std::string& property)
{
return DbusEnvironment::getProperty<T>(path, Trigger::triggerIfaceName,
property);
}
template <class T>
static boost::system::error_code setProperty(const std::string& path,
const std::string& property,
const T& newValue)
{
return DbusEnvironment::setProperty<T>(path, Trigger::triggerIfaceName,
property, newValue);
}
template <class T>
struct ChangePropertyParams
{
Matcher<T> valueBefore = _;
T newValue;
Matcher<boost::system::error_code> ec =
Eq(boost::system::errc::success);
Matcher<T> valueAfter = Eq(newValue);
};
template <class T>
static void changeProperty(const std::string& path,
const std::string& property,
ChangePropertyParams<T> p)
{
ASSERT_THAT(getProperty<T>(path, property), p.valueBefore);
ASSERT_THAT(setProperty<T>(path, property, p.newValue), p.ec);
EXPECT_THAT(getProperty<T>(path, property), p.valueAfter);
}
boost::system::error_code deleteTrigger(const std::string& path)
{
std::promise<boost::system::error_code> methodPromise;
DbusEnvironment::getBus()->async_method_call(
[&methodPromise](boost::system::error_code ec) {
methodPromise.set_value(ec);
},
DbusEnvironment::serviceName(), path, Trigger::deleteIfaceName,
"Delete");
return DbusEnvironment::waitForFuture(methodPromise.get_future());
}
};
TEST_F(TestTrigger, checkIfPropertiesAreSet)
{
EXPECT_THAT(getProperty<std::string>(sut->getPath(), "Name"),
Eq(triggerParams.name()));
EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistent"), Eq(true));
EXPECT_THAT(
getProperty<std::vector<std::string>>(sut->getPath(), "TriggerActions"),
Eq(utils::transform(
triggerParams.triggerActions(),
[](const auto& action) { return actionToString(action); })));
EXPECT_THAT((getProperty<SensorsInfo>(sut->getPath(), "Sensors")),
Eq(utils::fromLabeledSensorsInfo(triggerParams.sensors())));
EXPECT_THAT(
getProperty<std::vector<object_path>>(sut->getPath(), "Reports"),
Eq(triggerParams.reports()));
EXPECT_THAT(
getProperty<bool>(sut->getPath(), "Discrete"),
Eq(isTriggerThresholdDiscrete(triggerParams.thresholdParams())));
EXPECT_THAT(
getProperty<TriggerThresholdParams>(sut->getPath(), "Thresholds"),
Eq(std::visit(utils::FromLabeledThresholdParamConversion(),
triggerParams.thresholdParams())));
}
TEST_F(TestTrigger, checkBasicGetters)
{
EXPECT_THAT(sut->getId(), Eq(triggerParams.id()));
EXPECT_THAT(sut->getPath(),
Eq(utils::constants::triggerDirPath.str + triggerParams.id()));
}
TEST_F(TestTrigger, setPropertyNameToCorrectValue)
{
std::string name = "custom name 1234 %^#5";
EXPECT_THAT(setProperty(sut->getPath(), "Name", name),
Eq(boost::system::errc::success));
EXPECT_THAT(getProperty<std::string>(sut->getPath(), "Name"), Eq(name));
}
TEST_F(TestTrigger, setPropertyReportNames)
{
std::vector<object_path> newNames = {
utils::constants::reportDirPath / "abc",
utils::constants::reportDirPath / "one",
utils::constants::reportDirPath / "prefix" / "two"};
EXPECT_THAT(setProperty(sut->getPath(), "Reports", newNames),
Eq(boost::system::errc::success));
EXPECT_THAT(
getProperty<std::vector<object_path>>(sut->getPath(), "Reports"),
Eq(newNames));
}
TEST_F(TestTrigger, sendsUpdateWhenReportNamesChanges)
{
std::vector<object_path> newPropertyVal = {
utils::constants::reportDirPath / "abc",
utils::constants::reportDirPath / "one",
utils::constants::reportDirPath / "two"};
EXPECT_CALL(triggerPresenceChanged,
Call(FieldsAre(messages::Presence::Exist, triggerParams.id(),
UnorderedElementsAre("abc", "one", "two"))));
EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
Eq(boost::system::errc::success));
}
TEST_F(TestTrigger, sendsUpdateWhenReportNamesChangesToSameValue)
{
const std::vector<object_path> newPropertyVal = triggerParams.reports();
EXPECT_CALL(
triggerPresenceChanged,
Call(FieldsAre(messages::Presence::Exist, triggerParams.id(),
UnorderedElementsAreArray(triggerParams.reportIds()))));
EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
Eq(boost::system::errc::success));
}
TEST_F(TestTrigger,
DISABLED_settingPropertyReportNamesThrowsExceptionWhenDuplicateReportIds)
{
std::vector<object_path> newPropertyVal{
utils::constants::reportDirPath / "report1",
utils::constants::reportDirPath / "report2",
utils::constants::reportDirPath / "report1"};
EXPECT_CALL(triggerPresenceChanged, Call(_)).Times(0);
EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
Eq(boost::system::errc::invalid_argument));
}
TEST_F(
TestTrigger,
DISABLED_settingPropertyReportNamesThrowsExceptionWhenReportWithTooManyPrefixes)
{
std::vector<object_path> newPropertyVal{
object_path("/xyz/openbmc_project/Telemetry/Reports/P1/P2/MyReport")};
EXPECT_CALL(triggerPresenceChanged, Call(_)).Times(0);
EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
Eq(boost::system::errc::invalid_argument));
}
TEST_F(
TestTrigger,
DISABLED_settingPropertyReportNamesThrowsExceptionWhenReportWithTooLongPrefix)
{
std::vector<object_path> newPropertyVal{
object_path("/xyz/openbmc_project/Telemetry/Reports/" +
utils::string_utils::getTooLongPrefix() + "/MyReport")};
EXPECT_CALL(triggerPresenceChanged, Call(_)).Times(0);
EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
Eq(boost::system::errc::invalid_argument));
}
TEST_F(
TestTrigger,
DISABLED_settingPropertyReportNamesThrowsExceptionWhenReportWithTooLongId)
{
std::vector<object_path> newPropertyVal{
object_path("/xyz/openbmc_project/Telemetry/Reports/Prefix/" +
utils::string_utils::getTooLongId())};
EXPECT_CALL(triggerPresenceChanged, Call(_)).Times(0);
EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
Eq(boost::system::errc::invalid_argument));
}
TEST_F(TestTrigger,
DISABLED_settingPropertyReportNamesThrowsExceptionWhenReportWithBadPath)
{
std::vector<object_path> newPropertyVal{
object_path("/xyz/openbmc_project/Telemetry/NotReports/MyReport")};
EXPECT_CALL(triggerPresenceChanged, Call(_)).Times(0);
EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
Eq(boost::system::errc::invalid_argument));
}
TEST_F(TestTrigger, setPropertySensors)
{
EXPECT_CALL(*triggerFactoryMockPtr, updateSensors(_, _));
for (const auto& threshold : thresholdMocks)
{
auto thresholdMockPtr =
std::dynamic_pointer_cast<NiceMock<ThresholdMock>>(threshold);
EXPECT_CALL(*thresholdMockPtr, updateSensors(_));
}
SensorsInfo newSensors(
{std::make_pair(object_path("/abc/def"), "metadata")});
EXPECT_THAT(setProperty(sut->getPath(), "Sensors", newSensors),
Eq(boost::system::errc::success));
}
TEST_F(TestTrigger, setPropertyThresholds)
{
EXPECT_CALL(*triggerFactoryMockPtr, updateThresholds(_, _, _, _, _, _));
TriggerThresholdParams newThresholds =
std::vector<discrete::ThresholdParam>({std::make_tuple(
"discrete threshold", utils::enumToString(discrete::Severity::ok),
10, "12.3")});
EXPECT_THAT(setProperty(sut->getPath(), "Thresholds", newThresholds),
Eq(boost::system::errc::success));
}
TEST_F(TestTrigger, setThresholdParamsWithTooLongDiscreteName)
{
const TriggerThresholdParams currentValue =
std::visit(utils::FromLabeledThresholdParamConversion(),
triggerParams.thresholdParams());
TriggerThresholdParams newThresholds =
std::vector<discrete::ThresholdParam>({std::make_tuple(
utils::string_utils::getTooLongName(),
utils::enumToString(discrete::Severity::ok), 10, "12.3")});
changeProperty<TriggerThresholdParams>(
sut->getPath(), "Thresholds",
{.valueBefore = Eq(currentValue),
.newValue = newThresholds,
.ec = Eq(boost::system::errc::invalid_argument),
.valueAfter = Eq(currentValue)});
}
TEST_F(TestTrigger, setNameTooLong)
{
std::string currentValue = TriggerParams().name();
changeProperty<std::string>(
sut->getPath(), "Name",
{.valueBefore = Eq(currentValue),
.newValue = utils::string_utils::getTooLongName(),
.ec = Eq(boost::system::errc::invalid_argument),
.valueAfter = Eq(currentValue)});
}
TEST_F(TestTrigger, checkIfNumericCoversionsAreGood)
{
const auto& labeledParamsBase =
std::get<std::vector<numeric::LabeledThresholdParam>>(
triggerParams.thresholdParams());
const auto paramsToCheck =
std::visit(utils::FromLabeledThresholdParamConversion(),
triggerParams.thresholdParams());
const auto labeledParamsToCheck =
std::get<std::vector<numeric::LabeledThresholdParam>>(std::visit(
utils::ToLabeledThresholdParamConversion(), paramsToCheck));
for (const auto& [tocheck, base] :
boost::combine(labeledParamsToCheck, labeledParamsBase))
{
EXPECT_THAT(tocheck.at_label<utils::tstring::Type>(),
Eq(base.at_label<utils::tstring::Type>()));
EXPECT_THAT(tocheck.at_label<utils::tstring::Direction>(),
Eq(base.at_label<utils::tstring::Direction>()));
EXPECT_THAT(tocheck.at_label<utils::tstring::DwellTime>(),
Eq(base.at_label<utils::tstring::DwellTime>()));
EXPECT_THAT(tocheck.at_label<utils::tstring::ThresholdValue>(),
Eq(base.at_label<utils::tstring::ThresholdValue>()));
}
}
TEST_F(TestTrigger, checkIfDiscreteCoversionsAreGood)
{
const auto& labeledParamsBase =
std::get<std::vector<discrete::LabeledThresholdParam>>(
triggerDiscreteParams.thresholdParams());
const auto paramsToCheck =
std::visit(utils::FromLabeledThresholdParamConversion(),
triggerDiscreteParams.thresholdParams());
const auto labeledParamsToCheck =
std::get<std::vector<discrete::LabeledThresholdParam>>(std::visit(
utils::ToLabeledThresholdParamConversion(), paramsToCheck));
for (const auto& [tocheck, base] :
boost::combine(labeledParamsToCheck, labeledParamsBase))
{
EXPECT_THAT(tocheck.at_label<utils::tstring::UserId>(),
Eq(base.at_label<utils::tstring::UserId>()));
EXPECT_THAT(tocheck.at_label<utils::tstring::Severity>(),
Eq(base.at_label<utils::tstring::Severity>()));
EXPECT_THAT(tocheck.at_label<utils::tstring::DwellTime>(),
Eq(base.at_label<utils::tstring::DwellTime>()));
EXPECT_THAT(tocheck.at_label<utils::tstring::ThresholdValue>(),
Eq(base.at_label<utils::tstring::ThresholdValue>()));
}
}
TEST_F(TestTrigger, deleteTrigger)
{
EXPECT_CALL(storageMock, remove(to_file_path(sut->getId())));
EXPECT_CALL(*triggerManagerMockPtr, removeTrigger(sut.get()));
auto ec = deleteTrigger(sut->getPath());
EXPECT_THAT(ec, Eq(boost::system::errc::success));
}
TEST_F(TestTrigger, sendUpdateWhenTriggerIsDeleted)
{
EXPECT_CALL(triggerPresenceChanged,
Call(FieldsAre(messages::Presence::Removed, triggerParams.id(),
UnorderedElementsAre())));
auto ec = deleteTrigger(sut->getPath());
EXPECT_THAT(ec, Eq(boost::system::errc::success));
}
TEST_F(TestTrigger, deletingNonExistingTriggerReturnInvalidRequestDescriptor)
{
auto ec =
deleteTrigger(utils::constants::triggerDirPath.str + "NonExisting"s);
EXPECT_THAT(ec.value(), Eq(EBADR));
}
TEST_F(TestTrigger, settingPersistencyToFalseRemovesTriggerFromStorage)
{
EXPECT_CALL(storageMock, remove(to_file_path(sut->getId())));
bool persistent = false;
EXPECT_THAT(setProperty(sut->getPath(), "Persistent", persistent),
Eq(boost::system::errc::success));
EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistent"),
Eq(persistent));
}
class TestTriggerInitialization : public TestTrigger
{
public:
void SetUp() override {}
nlohmann::json storedConfiguration;
};
TEST_F(TestTriggerInitialization,
exceptionDuringTriggerStoreDisablesPersistency)
{
EXPECT_CALL(storageMock, store(_, _))
.WillOnce(Throw(std::runtime_error("Generic error!")));
sut = makeTrigger(triggerParams);
EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistent"), Eq(false));
}
TEST_F(TestTriggerInitialization, creatingTriggerThrowsExceptionWhenIdIsInvalid)
{
EXPECT_CALL(storageMock, store(_, _)).Times(0);
EXPECT_THROW(makeTrigger(triggerParams.id("inv?lidId")),
sdbusplus::exception::SdBusError);
}
TEST_F(TestTriggerInitialization, creatingTriggerUpdatesTriggersIdsInReports)
{
EXPECT_CALL(
triggerPresenceChanged,
Call(FieldsAre(messages::Presence::Exist, triggerParams.id(),
UnorderedElementsAreArray(triggerParams.reportIds()))));
sut = makeTrigger(triggerParams);
}
class TestTriggerStore : public TestTrigger
{
public:
nlohmann::json storedConfiguration;
nlohmann::json storedDiscreteConfiguration;
std::unique_ptr<Trigger> sutDiscrete;
void SetUp() override
{
ON_CALL(storageMock, store(_, _))
.WillByDefault(SaveArg<1>(&storedConfiguration));
sut = makeTrigger(triggerParams);
ON_CALL(storageMock, store(_, _))
.WillByDefault(SaveArg<1>(&storedDiscreteConfiguration));
sutDiscrete = makeTrigger(triggerDiscreteParams);
}
};
TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerVersion)
{
ASSERT_THAT(storedConfiguration.at("Version"), Eq(expectedTriggerVersion));
}
TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerId)
{
ASSERT_THAT(storedConfiguration.at("Id"), Eq(triggerParams.id()));
}
TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerName)
{
ASSERT_THAT(storedConfiguration.at("Name"), Eq(triggerParams.name()));
}
TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerTriggerActions)
{
ASSERT_THAT(storedConfiguration.at("TriggerActions"),
Eq(utils::transform(triggerParams.triggerActions(),
[](const auto& action) {
return actionToString(action);
})));
}
TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerReportIds)
{
ASSERT_THAT(storedConfiguration.at("ReportIds"),
Eq(triggerParams.reportIds()));
}
TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerSensors)
{
nlohmann::json expectedItem;
expectedItem["service"] = "service1";
expectedItem["path"] = "/xyz/openbmc_project/sensors/temperature/BMC_Temp";
expectedItem["metadata"] = "metadata1";
ASSERT_THAT(storedConfiguration.at("Sensors"), ElementsAre(expectedItem));
}
TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerThresholdParams)
{
nlohmann::json expectedItem0;
expectedItem0["type"] = 0;
expectedItem0["dwellTime"] = 10;
expectedItem0["direction"] = 1;
expectedItem0["thresholdValue"] = 0.5;
nlohmann::json expectedItem1;
expectedItem1["type"] = 3;
expectedItem1["dwellTime"] = 10;
expectedItem1["direction"] = 2;
expectedItem1["thresholdValue"] = 90.2;
ASSERT_THAT(storedConfiguration.at("ThresholdParamsDiscriminator"), Eq(0));
ASSERT_THAT(storedConfiguration.at("ThresholdParams"),
ElementsAre(expectedItem0, expectedItem1));
}
TEST_F(TestTriggerStore,
settingPersistencyToTrueStoresDiscreteTriggerThresholdParams)
{
nlohmann::json expectedItem0;
expectedItem0["userId"] = "userId";
expectedItem0["severity"] = discrete::Severity::warning;
expectedItem0["dwellTime"] = 10;
expectedItem0["thresholdValue"] = "15.2";
nlohmann::json expectedItem1;
expectedItem1["userId"] = "userId_2";
expectedItem1["severity"] = discrete::Severity::critical;
expectedItem1["dwellTime"] = 5;
expectedItem1["thresholdValue"] = "32.7";
ASSERT_THAT(storedDiscreteConfiguration.at("ThresholdParamsDiscriminator"),
Eq(1));
ASSERT_THAT(storedDiscreteConfiguration.at("ThresholdParams"),
ElementsAre(expectedItem0, expectedItem1));
}