Skip to content

Commit

Permalink
Test
Browse files Browse the repository at this point in the history
  • Loading branch information
julianbrost committed Feb 4, 2022
1 parent a9ff016 commit 5709d9c
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 20 deletions.
36 changes: 18 additions & 18 deletions lib/icinga/checkable-notification.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,34 +129,34 @@ void Checkable::UnregisterNotification(const Notification::Ptr& notification)
m_Notifications.erase(notification);
}

static void FireSuppressedNotifications(Checkable* checkable)
void Checkable::FireSuppressedNotifications()
{
if (!checkable->IsActive())
if (!IsActive())
return;

if (checkable->IsPaused())
if (IsPaused())
return;

if (!checkable->GetEnableNotifications())
if (!GetEnableNotifications())
return;

int suppressed_types (checkable->GetSuppressedNotifications());
int suppressed_types (GetSuppressedNotifications());
if (!suppressed_types)
return;

int subtract = 0;

{
LazyInit<bool> wasLastParentRecoveryRecent ([&checkable]() {
auto cr (checkable->GetLastCheckResult());
LazyInit<bool> wasLastParentRecoveryRecent ([this]() {
auto cr (GetLastCheckResult());

if (!cr) {
return true;
}

auto threshold (cr->GetExecutionStart());

for (auto& dep : checkable->GetDependencies()) {
for (auto& dep : GetDependencies()) {
auto parent (dep->GetParent());
ObjectLock oLock (parent);

Expand All @@ -170,7 +170,7 @@ static void FireSuppressedNotifications(Checkable* checkable)

for (auto type : {NotificationProblem, NotificationRecovery, NotificationFlappingStart, NotificationFlappingEnd}) {
if (suppressed_types & type) {
if (type & (NotificationProblem|NotificationRecovery) && checkable->GetStateType() != StateTypeHard) {
if (type & (NotificationProblem|NotificationRecovery) && GetStateType() != StateTypeHard) {
/* If there are suppressed state notifications and the checkable currently is in a soft state,
* wait with sending the notification until a hard state is reached.
*
Expand All @@ -185,11 +185,11 @@ static void FireSuppressedNotifications(Checkable* checkable)
continue;
}

bool still_applies = checkable->NotificationReasonApplies(type);
bool still_applies = NotificationReasonApplies(type);

if (still_applies) {
if (!checkable->NotificationReasonSuppressed(type) && !checkable->IsLikelyToBeCheckedSoon() && !wasLastParentRecoveryRecent.Get()) {
Checkable::OnNotificationsRequested(checkable, type, checkable->GetLastCheckResult(), "", "", nullptr);
if (!NotificationReasonSuppressed(type) && !IsLikelyToBeCheckedSoon() && !wasLastParentRecoveryRecent.Get()) {
Checkable::OnNotificationsRequested(this, type, GetLastCheckResult(), "", "", nullptr);

subtract |= type;
}
Expand All @@ -201,28 +201,28 @@ static void FireSuppressedNotifications(Checkable* checkable)
}

if (subtract) {
ObjectLock olock (checkable);
ObjectLock olock (this);

int suppressed_types_before (checkable->GetSuppressedNotifications());
int suppressed_types_before (GetSuppressedNotifications());
int suppressed_types_after (suppressed_types_before & ~subtract);

if (suppressed_types_after != suppressed_types_before) {
checkable->SetSuppressedNotifications(suppressed_types_after);
SetSuppressedNotifications(suppressed_types_after);
}
}
}

/**
* Re-sends all notifications previously suppressed by e.g. downtimes if the notification reason still applies.
*/
void Checkable::FireSuppressedNotifications(const Timer * const&)
void Checkable::FireSuppressedNotificationsTimer(const Timer * const&)
{
for (auto& host : ConfigType::GetObjectsByType<Host>()) {
::FireSuppressedNotifications(host.get());
host->FireSuppressedNotifications();
}

for (auto& service : ConfigType::GetObjectsByType<Service>()) {
::FireSuppressedNotifications(service.get());
service->FireSuppressedNotifications();
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/icinga/checkable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ void Checkable::Start(bool runtimeCreated)
boost::call_once(once, []() {
l_CheckablesFireSuppressedNotifications = new Timer();
l_CheckablesFireSuppressedNotifications->SetInterval(5);
l_CheckablesFireSuppressedNotifications->OnTimerExpired.connect(&Checkable::FireSuppressedNotifications);
l_CheckablesFireSuppressedNotifications->OnTimerExpired.connect(&Checkable::FireSuppressedNotificationsTimer);
l_CheckablesFireSuppressedNotifications->Start();

l_CleanDeadlinedExecutions = new Timer();
Expand Down
14 changes: 13 additions & 1 deletion lib/icinga/checkable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
#include <cstdint>
#include <functional>

#if I2_DEBUG
namespace icinga_checkresult {
struct suppressed_notification;
}
#endif

namespace icinga
{

Expand Down Expand Up @@ -222,7 +228,7 @@ class Checkable : public ObjectImpl<Checkable>

static void NotifyDowntimeEnd(const Downtime::Ptr& downtime);

static void FireSuppressedNotifications(const Timer * const&);
static void FireSuppressedNotificationsTimer(const Timer * const&);
static void CleanDeadlinedExecutions(const Timer * const&);

/* Comments */
Expand All @@ -233,6 +239,12 @@ class Checkable : public ObjectImpl<Checkable>
std::set<Notification::Ptr> m_Notifications;
mutable std::mutex m_NotificationMutex;

void FireSuppressedNotifications();

#if I2_DEBUG
friend struct icinga_checkresult::suppressed_notification;
#endif

/* Dependencies */
mutable std::mutex m_DependencyMutex;
std::set<intrusive_ptr<Dependency> > m_Dependencies;
Expand Down
11 changes: 11 additions & 0 deletions lib/icinga/downtime.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
#include "icinga/checkable-ti.hpp"
#include "remote/messageorigin.hpp"

#if I2_DEBUG
namespace icinga_checkresult {
struct suppressed_notification;
}
#endif

namespace icinga
{

Expand Down Expand Up @@ -82,6 +88,11 @@ class Downtime final : public ObjectImpl<Downtime>

bool CanBeTriggered();

#if I2_DEBUG
void SetCheckable(const intrusive_ptr<Checkable>& checkable) { m_Checkable = static_pointer_cast<ObjectImpl<Checkable>>(checkable); }
friend struct icinga_checkresult::suppressed_notification;
#endif

static void DowntimesStartTimerHandler();
static void DowntimesExpireTimerHandler();
};
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ add_boost_test(base
icinga_checkresult/service_3attempts
icinga_checkresult/host_flapping_notification
icinga_checkresult/service_flapping_notification
icinga_checkresult/suppressed_notification
icinga_dependencies/multi_parent
icinga_notification/strings
icinga_notification/state_filter
Expand Down
198 changes: 198 additions & 0 deletions test/icinga-checkresult.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
#include "icinga/host.hpp"
#include <BoostTestTargetConfig.h>
#include <iostream>
#include <sstream>
#include <unordered_map>
#include <utility>
#include <vector>

using namespace icinga;

Expand Down Expand Up @@ -809,4 +813,198 @@ BOOST_AUTO_TEST_CASE(service_flapping_ok_over_bad_into_ok)

#endif /* I2_DEBUG */
}

class StateSequenceGenerator {
public:
StateSequenceGenerator(size_t num, std::vector<ServiceState> availableStates)
: availableStates(std::move(availableStates)), progress(num), done(false) {}

std::vector<ServiceState> operator()() {
if (done) {
return {};
}

std::vector<ServiceState> result(progress.size());

for (size_t i = 0; i < progress.size(); i++) {
result[i] = availableStates[progress[i]];
}

bool overflow = true;
for (size_t i = 0; i < progress.size(); i++) {
if (++progress[i] < availableStates.size()) {
overflow = false;
break;
} else {
progress[i] = 0;
}
}
done = overflow;

return result;
}

explicit operator bool() const {
return !done;
}

void reset() {
std::fill(progress.begin(), progress.end(), 0);
done = false;
}

private:
std::vector<ServiceState> availableStates;
std::vector<size_t> progress;
bool done;
};

BOOST_AUTO_TEST_CASE(suppressed_notification)
{
#ifndef I2_DEBUG
BOOST_WARN_MESSAGE(false, "This test can only be run in a debug build!");
#else /* I2_DEBUG */

std::unordered_map<Checkable::Ptr, std::vector<std::pair<NotificationType, ServiceState>>> requestedNotifications;

auto c = Checkable::OnNotificationsRequested.connect([&requestedNotifications](
const Checkable::Ptr& checkable, NotificationType type, const CheckResult::Ptr& cr,
const String&, const String&, const MessageOrigin::Ptr&
) {
std::cout << " -> OnNotificationsRequested(" << Notification::NotificationTypeToString(type) << ", " << Service::StateToString(cr->GetState()) << ")" << std::endl;
requestedNotifications[checkable].emplace_back(type, cr->GetState());
});

auto assertNotifications = [&requestedNotifications](
const Checkable::Ptr& checkable,
const std::vector<std::pair<NotificationType, ServiceState>>& expected,
const std::string& extraMessage
) {
auto pretty = [](const std::vector<std::pair<NotificationType, ServiceState>>& vec) {
std::ostringstream s;

s << "{";
bool first = true;
for (const auto &v : vec) {
if (first) {
first = false;
} else {
s << ", ";
}
s << Notification::NotificationTypeToString(v.first)
<< "/" << Service::StateToString(v.second);
}
s << "}";

return s.str();
};

auto& got = requestedNotifications[checkable];
BOOST_CHECK_MESSAGE(got == expected, "expected=" << pretty(expected)
<< " got=" << pretty(got) << (extraMessage.empty() ? "" : " ") << extraMessage);

requestedNotifications.erase(checkable);
};

StateSequenceGenerator stateSeqGen(6, {ServiceOK, ServiceWarning, ServiceCritical, ServiceUnknown});

for (bool isVolatile : {false, true}) {
for (int checkAttempts : {1, 2}) {
stateSeqGen.reset();
while (stateSeqGen) {
const std::vector<ServiceState> sequence = stateSeqGen();

std::string testcase;
{
std::ostringstream buf;
buf << "volatile=" << isVolatile << " checkAttempts=" << checkAttempts << " sequence={";
bool first = true;
for (ServiceState s: sequence) {
if (!first) {
buf << " ";
}
buf << Service::StateToString(s);
first = false;
}
buf << "}";
testcase = buf.str();
}
std::cout << "Test case: " << testcase << std::endl;

Service::Ptr service = new Service();
service->SetActive(true);
service->SetVolatile(isVolatile);
service->SetMaxCheckAttempts(checkAttempts);
service->Activate();
service->SetAuthority(true);
service->SetStateRaw(sequence.front());
service->SetStateType(StateTypeHard);
service->SetEnableActiveChecks(false); // TODO: maybe needed due to LikelyToBeCheckedSoon
BOOST_CHECK(service->GetState() == sequence.front());
BOOST_CHECK(service->GetStateType() == StateTypeHard);

std::cout << " Downtime Start" << std::endl;
Downtime::Ptr downtime = new Downtime();
downtime->SetCheckable(service);
downtime->SetFixed(true);
downtime->SetStartTime(Utility::GetTime() - 3600);
downtime->SetEndTime(Utility::GetTime() + 3600);
service->RegisterDowntime(downtime);
BOOST_CHECK(service->IsInDowntime());

bool first = true;
for (ServiceState s: sequence) {
if (first) {
// Don't process check result for initial state as it was set above.
first = false;
continue;
}
std::cout << " ProcessCheckResult(" << Service::StateToString(s) << ")" << std::endl;
service->ProcessCheckResult(MakeCheckResult(s));
BOOST_CHECK(service->GetState() == s);
if (checkAttempts == 1) {
BOOST_CHECK(service->GetStateType() == StateTypeHard);
}
}

assertNotifications(service, {}, "(no notifications in downtime)");

BOOST_CHECK(!service->GetSuppressedNotifications() || service->GetStateBeforeSuppression() == sequence.front());

std::cout << " Downtime End" << std::endl;
service->UnregisterDowntime(downtime);
BOOST_CHECK(!service->IsInDowntime());

std::cout << " FireSuppressedNotifications()" << std::endl;
service->FireSuppressedNotifications();

if (service->GetStateType() == icinga::StateTypeSoft) {
assertNotifications(service, {}, testcase + " (should not fire in soft state)");

for (int i = 0; i < checkAttempts && service->GetStateType() == StateTypeSoft; i++) {
std::cout << " ProcessCheckResult(" << Service::StateToString(sequence.back()) << ")" << std::endl;
service->ProcessCheckResult(MakeCheckResult(sequence.back()));
BOOST_CHECK(service->GetState() == sequence.back());
}

std::cout << " FireSuppressedNotifications()" << std::endl;
service->FireSuppressedNotifications();
}

BOOST_CHECK(service->GetStateType() == StateTypeHard);

if (sequence.front() != sequence.back()) {
assertNotifications(service, {
{sequence.back() == ServiceOK ? NotificationRecovery : NotificationProblem, sequence.back()}
}, testcase);
} else {
assertNotifications(service, {}, testcase);
}
}
}
}

c.disconnect();
#endif /* I2_DEBUG */
}
BOOST_AUTO_TEST_SUITE_END()

0 comments on commit 5709d9c

Please sign in to comment.