|
1 | 1 | /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
2 | 2 |
|
| 3 | +#include "icinga/downtime.hpp" |
3 | 4 | #include "icinga/host.hpp"
|
| 5 | +#include "icinga/service.hpp" |
4 | 6 | #include <BoostTestTargetConfig.h>
|
5 | 7 | #include <iostream>
|
| 8 | +#include <sstream> |
| 9 | +#include <utility> |
| 10 | +#include <vector> |
6 | 11 |
|
7 | 12 | using namespace icinga;
|
8 | 13 |
|
@@ -809,4 +814,184 @@ BOOST_AUTO_TEST_CASE(service_flapping_ok_over_bad_into_ok)
|
809 | 814 |
|
810 | 815 | #endif /* I2_DEBUG */
|
811 | 816 | }
|
| 817 | + |
| 818 | +BOOST_AUTO_TEST_CASE(suppressed_notification) |
| 819 | +{ |
| 820 | + /* Tests that suppressed notifications on a Checkable are sent after the suppression ends if and only if the first |
| 821 | + * hard state after the suppression is different from the last hard state before the suppression. The test works |
| 822 | + * by bringing a service in a defined hard state, creating a downtime, performing some state changes, removing the |
| 823 | + * downtime, bringing the service into another defined hard state (if not already) and checking the requested |
| 824 | + * notifications. |
| 825 | + */ |
| 826 | + |
| 827 | + const std::vector<ServiceState> states {ServiceOK, ServiceWarning, ServiceCritical, ServiceUnknown}; |
| 828 | + |
| 829 | + for (bool isVolatile : {false, true}) { |
| 830 | + for (int checkAttempts : {1, 2}) { |
| 831 | + for (ServiceState initialState: states) { |
| 832 | + for (auto s1 : states) for (auto s2 : states) for (auto s3 : states) for (auto s4 : states) { |
| 833 | + const std::vector<ServiceState> sequence {s1, s2, s3, s4}; |
| 834 | + |
| 835 | + std::string testcase; |
| 836 | + { |
| 837 | + std::ostringstream buf; |
| 838 | + buf << "volatile=" << isVolatile |
| 839 | + << " checkAttempts=" << checkAttempts |
| 840 | + << " sequence={" << Service::StateToString(initialState); |
| 841 | + for (ServiceState s: sequence) { |
| 842 | + buf << " " << Service::StateToString(s); |
| 843 | + } |
| 844 | + buf << "}"; |
| 845 | + testcase = buf.str(); |
| 846 | + } |
| 847 | + std::cout << "Test case: " << testcase << std::endl; |
| 848 | + |
| 849 | + // Create host and service for the test. |
| 850 | + Host::Ptr host = new Host(); |
| 851 | + host->SetName("suppressed_notifications"); |
| 852 | + host->Register(); |
| 853 | + |
| 854 | + Service::Ptr service = new Service(); |
| 855 | + service->SetHostName(host->GetName()); |
| 856 | + service->SetName("service"); |
| 857 | + service->SetActive(true); |
| 858 | + service->SetVolatile(isVolatile); |
| 859 | + service->SetMaxCheckAttempts(checkAttempts); |
| 860 | + service->Activate(); |
| 861 | + service->SetAuthority(true); |
| 862 | + service->SetEnableActiveChecks(false); // TODO: maybe needed due to LikelyToBeCheckedSoon |
| 863 | + service->Register(); |
| 864 | + |
| 865 | + dynamic_pointer_cast<ConfigObject>(host)->OnAllConfigLoaded(); |
| 866 | + dynamic_pointer_cast<ConfigObject>(service)->OnAllConfigLoaded(); |
| 867 | + |
| 868 | + // Bring service into the initial hard state. |
| 869 | + for (int i = 0; i < checkAttempts; i++) { |
| 870 | + std::cout << " ProcessCheckResult(" |
| 871 | + << Service::StateToString(initialState) << ")" << std::endl; |
| 872 | + service->ProcessCheckResult(MakeCheckResult(initialState)); |
| 873 | + } |
| 874 | + BOOST_CHECK(service->GetState() == initialState); |
| 875 | + BOOST_CHECK(service->GetStateType() == StateTypeHard); |
| 876 | + |
| 877 | + // Keep track of all notifications requested from now on. |
| 878 | + std::vector<std::pair<NotificationType, ServiceState>> requestedNotifications; |
| 879 | + boost::signals2::scoped_connection c (Checkable::OnNotificationsRequested.connect([&]( |
| 880 | + const Checkable::Ptr& checkable, NotificationType type, const CheckResult::Ptr& cr, |
| 881 | + const String&, const String&, const MessageOrigin::Ptr& |
| 882 | + ) { |
| 883 | + BOOST_CHECK_EQUAL(checkable, service); |
| 884 | + std::cout << " -> OnNotificationsRequested(" << Notification::NotificationTypeToString(type) |
| 885 | + << ", " << Service::StateToString(cr->GetState()) << ")" << std::endl; |
| 886 | + requestedNotifications.emplace_back(type, cr->GetState()); |
| 887 | + })); |
| 888 | + |
| 889 | + // Helper to assert which notifications were requested. Implicitly clears the stored notifications. |
| 890 | + auto assertNotifications = [&]( |
| 891 | + const std::vector<std::pair<NotificationType, ServiceState>>& expected, |
| 892 | + const std::string& extraMessage |
| 893 | + ) { |
| 894 | + // Pretty-printer for the vectors of requested and expected notifications. |
| 895 | + auto pretty = [](const std::vector<std::pair<NotificationType, ServiceState>>& vec) { |
| 896 | + std::ostringstream s; |
| 897 | + |
| 898 | + s << "{"; |
| 899 | + bool first = true; |
| 900 | + for (const auto &v : vec) { |
| 901 | + if (first) { |
| 902 | + first = false; |
| 903 | + } else { |
| 904 | + s << ", "; |
| 905 | + } |
| 906 | + s << Notification::NotificationTypeToString(v.first) |
| 907 | + << "/" << Service::StateToString(v.second); |
| 908 | + } |
| 909 | + s << "}"; |
| 910 | + |
| 911 | + return s.str(); |
| 912 | + }; |
| 913 | + |
| 914 | + BOOST_CHECK_MESSAGE(requestedNotifications == expected, "expected=" << pretty(expected) |
| 915 | + << " got=" << pretty(requestedNotifications) |
| 916 | + << (extraMessage.empty() ? "" : " ") << extraMessage); |
| 917 | + |
| 918 | + requestedNotifications.clear(); |
| 919 | + }; |
| 920 | + |
| 921 | + // Start a downtime for the service. |
| 922 | + std::cout << " Downtime Start" << std::endl; |
| 923 | + Downtime::Ptr downtime = new Downtime(); |
| 924 | + downtime->SetHostName(host->GetName()); |
| 925 | + downtime->SetServiceName(service->GetName()); |
| 926 | + downtime->SetName("downtime"); |
| 927 | + downtime->SetFixed(true); |
| 928 | + downtime->SetStartTime(Utility::GetTime() - 3600); |
| 929 | + downtime->SetEndTime(Utility::GetTime() + 3600); |
| 930 | + service->RegisterDowntime(downtime); |
| 931 | + downtime->Register(); |
| 932 | + dynamic_pointer_cast<ConfigObject>(downtime)->OnAllConfigLoaded(); |
| 933 | + downtime->TriggerDowntime(Utility::GetTime()); |
| 934 | + |
| 935 | + BOOST_CHECK(service->IsInDowntime()); |
| 936 | + |
| 937 | + // Process check results for the state sequence. |
| 938 | + for (ServiceState s: sequence) { |
| 939 | + std::cout << " ProcessCheckResult(" << Service::StateToString(s) << ")" << std::endl; |
| 940 | + service->ProcessCheckResult(MakeCheckResult(s)); |
| 941 | + BOOST_CHECK(service->GetState() == s); |
| 942 | + if (checkAttempts == 1) { |
| 943 | + BOOST_CHECK(service->GetStateType() == StateTypeHard); |
| 944 | + } |
| 945 | + } |
| 946 | + |
| 947 | + assertNotifications({}, "(no notifications in downtime)"); |
| 948 | + |
| 949 | + if (service->GetSuppressedNotifications()) { |
| 950 | + BOOST_CHECK_EQUAL(service->GetStateBeforeSuppression(), initialState); |
| 951 | + } |
| 952 | + |
| 953 | + // Remove the downtime. |
| 954 | + std::cout << " Downtime End" << std::endl; |
| 955 | + service->UnregisterDowntime(downtime); |
| 956 | + downtime->Unregister(); |
| 957 | + BOOST_CHECK(!service->IsInDowntime()); |
| 958 | + |
| 959 | + if (service->GetStateType() == icinga::StateTypeSoft) { |
| 960 | + // When the current state is a soft state, no notification should be sent just yet. |
| 961 | + std::cout << " FireSuppressedNotifications()" << std::endl; |
| 962 | + service->FireSuppressedNotifications(); |
| 963 | + |
| 964 | + assertNotifications({}, testcase + " (should not fire in soft state)"); |
| 965 | + |
| 966 | + // Repeat the last check result until reaching a hard state. |
| 967 | + for (int i = 0; i < checkAttempts && service->GetStateType() == StateTypeSoft; i++) { |
| 968 | + std::cout << " ProcessCheckResult(" << Service::StateToString(sequence.back()) << ")" |
| 969 | + << std::endl; |
| 970 | + service->ProcessCheckResult(MakeCheckResult(sequence.back())); |
| 971 | + BOOST_CHECK(service->GetState() == sequence.back()); |
| 972 | + } |
| 973 | + } |
| 974 | + |
| 975 | + // The service should be in a hard state now and notifications should now be sent if applicable. |
| 976 | + BOOST_CHECK(service->GetStateType() == StateTypeHard); |
| 977 | + |
| 978 | + std::cout << " FireSuppressedNotifications()" << std::endl; |
| 979 | + service->FireSuppressedNotifications(); |
| 980 | + |
| 981 | + if (initialState != sequence.back()) { |
| 982 | + NotificationType t = sequence.back() == ServiceOK ? NotificationRecovery : NotificationProblem; |
| 983 | + assertNotifications({{t, sequence.back()}}, testcase); |
| 984 | + } else { |
| 985 | + assertNotifications({}, testcase); |
| 986 | + } |
| 987 | + |
| 988 | + // Remove host and service. |
| 989 | + service->Unregister(); |
| 990 | + host->Unregister(); |
| 991 | + } |
| 992 | + } |
| 993 | + } |
| 994 | + } |
| 995 | +} |
| 996 | + |
812 | 997 | BOOST_AUTO_TEST_SUITE_END()
|
0 commit comments