Merge pull request #10452 from Icinga/streamline-redis-and-db-values

IcingaDB: Make Redis & DB values consistent
This commit is contained in:
Julian Brost 2025-06-10 11:30:07 +02:00 committed by GitHub
commit c41fe682c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 211 additions and 77 deletions

View File

@ -130,7 +130,7 @@ void CompatLogger::CheckResultHandler(const Checkable::Ptr& checkable, const Che
<< host->GetName() << ";"
<< service->GetShortName() << ";"
<< Service::StateToString(service->GetState()) << ";"
<< Service::StateTypeToString(service->GetStateType()) << ";"
<< Checkable::StateTypeToString(service->GetStateType()) << ";"
<< attempt_after << ";"
<< output << ""
<< "";
@ -140,7 +140,7 @@ void CompatLogger::CheckResultHandler(const Checkable::Ptr& checkable, const Che
msgbuf << "HOST ALERT: "
<< host->GetName() << ";"
<< GetHostStateString(host) << ";"
<< Host::StateTypeToString(host->GetStateType()) << ";"
<< Checkable::StateTypeToString(host->GetStateType()) << ";"
<< attempt_after << ";"
<< output << ""
<< "";
@ -413,14 +413,14 @@ void CompatLogger::EventCommandHandler(const Checkable::Ptr& checkable)
<< host->GetName() << ";"
<< service->GetShortName() << ";"
<< Service::StateToString(service->GetState()) << ";"
<< Service::StateTypeToString(service->GetStateType()) << ";"
<< Checkable::StateTypeToString(service->GetStateType()) << ";"
<< current_attempt << ";"
<< event_command_name;
} else {
msgbuf << "HOST EVENT HANDLER: "
<< host->GetName() << ";"
<< GetHostStateString(host) << ";"
<< Host::StateTypeToString(host->GetStateType()) << ";"
<< Checkable::StateTypeToString(host->GetStateType()) << ";"
<< current_attempt << ";"
<< event_command_name;
}
@ -505,7 +505,7 @@ void CompatLogger::ReopenFile(bool rotate)
msgbuf << "CURRENT HOST STATE: "
<< host->GetName() << ";"
<< GetHostStateString(host) << ";"
<< Host::StateTypeToString(host->GetStateType()) << ";"
<< Checkable::StateTypeToString(host->GetStateType()) << ";"
<< host->GetCheckAttempt() << ";"
<< output << "";
@ -526,7 +526,7 @@ void CompatLogger::ReopenFile(bool rotate)
<< host->GetName() << ";"
<< service->GetShortName() << ";"
<< Service::StateToString(service->GetState()) << ";"
<< Service::StateTypeToString(service->GetStateType()) << ";"
<< Checkable::StateTypeToString(service->GetStateType()) << ";"
<< service->GetCheckAttempt() << ";"
<< output << "";

View File

@ -994,7 +994,7 @@ void DbEvents::AddCheckResultLogHistory(const Checkable::Ptr& checkable, const C
<< host->GetName() << ";"
<< service->GetShortName() << ";"
<< Service::StateToString(service->GetState()) << ";"
<< Service::StateTypeToString(service->GetStateType()) << ";"
<< Checkable::StateTypeToString(service->GetStateType()) << ";"
<< service->GetCheckAttempt() << ";"
<< output << ""
<< "";
@ -1021,7 +1021,7 @@ void DbEvents::AddCheckResultLogHistory(const Checkable::Ptr& checkable, const C
msgbuf << "HOST ALERT: "
<< host->GetName() << ";"
<< GetHostStateString(host) << ";"
<< Host::StateTypeToString(host->GetStateType()) << ";"
<< Checkable::StateTypeToString(host->GetStateType()) << ";"
<< host->GetCheckAttempt() << ";"
<< output << ""
<< "";

View File

@ -322,3 +322,9 @@ void Checkable::CleanDeadlinedExecutions(const Timer * const&)
}
}
}
String Checkable::StateTypeToString(StateType type)
{
return type == StateTypeSoft ? "SOFT" : "HARD";
}

View File

@ -106,6 +106,8 @@ public:
void UpdateNextCheck(const MessageOrigin::Ptr& origin = nullptr);
static String StateTypeToString(StateType type);
bool HasBeenChecked() const;
virtual bool IsStateOK(ServiceState state) const = 0;

View File

@ -227,22 +227,6 @@ String Host::StateToString(HostState state)
}
}
StateType Host::StateTypeFromString(const String& type)
{
if (type == "SOFT")
return StateTypeSoft;
else
return StateTypeHard;
}
String Host::StateTypeToString(StateType type)
{
if (type == StateTypeSoft)
return "SOFT";
else
return "HARD";
}
bool Host::ResolveMacro(const String& macro, const CheckResult::Ptr&, Value *result) const
{
if (macro == "state") {

View File

@ -45,9 +45,6 @@ public:
static HostState StateFromString(const String& state);
static String StateToString(HostState state);
static StateType StateTypeFromString(const String& state);
static String StateTypeToString(StateType state);
bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const override;
void OnAllConfigLoaded() override;

View File

@ -100,12 +100,13 @@ void Notification::StaticInitialize()
m_TypeFilterMap["FlappingEnd"] = NotificationFlappingEnd;
}
void Notification::OnConfigLoaded()
Notification::Notification()
{
ObjectImpl<Notification>::OnConfigLoaded();
SetTypeFilter(FilterArrayToInt(GetTypes(), GetTypeFilterMap(), ~0));
SetStateFilter(FilterArrayToInt(GetStates(), GetStateFilterMap(), ~0));
// If a notification is created without specifying the "types/states" attribute, the Set* methods won't be called,
// consequently the filter bitset will also be 0. Thus, we need to ensure that the type/state filter are
// initialized to the default values, which are all types and states enabled.
SetTypes(nullptr, false, Empty);
SetStates(nullptr, false, Empty);
}
void Notification::OnAllConfigLoaded()
@ -751,6 +752,36 @@ String Notification::NotificationHostStateToString(HostState state)
}
}
Array::Ptr Notification::GetTypes() const
{
return m_Types.load();
}
void Notification::SetTypes(const Array::Ptr& value, bool suppress_events, const Value& cookie)
{
m_Types.store(value);
// Ensure that the type filter is updated when the types attribute changes.
SetTypeFilter(FilterArrayToInt(value, GetTypeFilterMap(), ~0));
if (!suppress_events) {
NotifyTypes(cookie);
}
}
Array::Ptr Notification::GetStates() const
{
return m_States.load();
}
void Notification::SetStates(const Array::Ptr& value, bool suppress_events, const Value& cookie)
{
m_States.store(value);
// Ensure that the state filter is updated when the states attribute changes.
SetStateFilter(FilterArrayToInt(value, GetStateFilterMap(), ~0));
if (!suppress_events) {
NotifyStates(cookie);
}
}
void Notification::Validate(int types, const ValidationUtils& utils)
{
ObjectImpl<Notification>::Validate(types, utils);

View File

@ -29,7 +29,9 @@ enum NotificationFilter
StateFilterUnknown = 8,
StateFilterUp = 16,
StateFilterDown = 32
StateFilterDown = 32,
StateFilterAll = StateFilterOK | StateFilterWarning | StateFilterCritical | StateFilterUnknown | StateFilterUp | StateFilterDown,
};
/**
@ -47,7 +49,11 @@ enum NotificationType
NotificationProblem = 32,
NotificationRecovery = 64,
NotificationFlappingStart = 128,
NotificationFlappingEnd = 256
NotificationFlappingEnd = 256,
NotificationTypeAll = NotificationDowntimeStart | NotificationDowntimeEnd | NotificationDowntimeRemoved |
NotificationCustom | NotificationAcknowledgement | NotificationProblem | NotificationRecovery |
NotificationFlappingStart | NotificationFlappingEnd,
};
class NotificationCommand;
@ -68,6 +74,8 @@ public:
DECLARE_OBJECT(Notification);
DECLARE_OBJECTNAME(Notification);
Notification();
static void StaticInitialize();
intrusive_ptr<Checkable> GetCheckable() const;
@ -109,13 +117,28 @@ public:
static const std::map<String, int>& GetStateFilterMap();
static const std::map<String, int>& GetTypeFilterMap();
void OnConfigLoaded() override;
void OnAllConfigLoaded() override;
void Start(bool runtimeCreated) override;
void Stop(bool runtimeRemoved) override;
Array::Ptr GetTypes() const override;
void SetTypes(const Array::Ptr& value, bool suppress_events, const Value& cookie) override;
Array::Ptr GetStates() const override;
void SetStates(const Array::Ptr& value, bool suppress_events, const Value& cookie) override;
private:
ObjectImpl<Checkable>::Ptr m_Checkable;
// These attributes represent the actual notification "types" and "states" attributes from the "notification.ti".
// However, since we want to ensure that the type and state bitsets are always in sync with those attributes,
// we need to override their setters, and this on the hand introduces another problem: The virtual setters are
// called from within the ObjectImpl<Notification> constructor, which obviously violates the C++ standard [^1].
// So, in order to avoid all this kind of mess, these two attributes have the "no_storage" flag set, and
// their getters/setters are pure virtual, which means this class has to provide the implementation of them.
//
// [^1]: https://isocpp.org/wiki/faq/strange-inheritance#calling-virtuals-from-ctors
AtomicOrLocked<Array::Ptr> m_Types;
AtomicOrLocked<Array::Ptr> m_States;
bool CheckNotificationUserFilters(NotificationType type, const User::Ptr& user, bool force, bool reminder);

View File

@ -41,9 +41,15 @@ class Notification : CustomVarObject < NotificationNameComposer
[config, signal_with_old_value] array(name(User)) users (UsersRaw);
[config, signal_with_old_value] array(name(UserGroup)) user_groups (UserGroupsRaw);
[config] Dictionary::Ptr times;
[config] array(Value) types;
[config, no_storage] array(Value) types {
get;
set;
};
[no_user_view, no_user_modify] int type_filter_real (TypeFilter);
[config] array(Value) states;
[config, no_storage] array(Value) states {
get;
set;
};
[no_user_view, no_user_modify] int state_filter_real (StateFilter);
[config, no_user_modify, protected, required, navigation(host)] name(Host) host_name {
navigate {{{

View File

@ -195,22 +195,6 @@ String Service::StateToString(ServiceState state)
}
}
StateType Service::StateTypeFromString(const String& type)
{
if (type == "SOFT")
return StateTypeSoft;
else
return StateTypeHard;
}
String Service::StateTypeToString(StateType type)
{
if (type == StateTypeSoft)
return "SOFT";
else
return "HARD";
}
bool Service::ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const
{
if (macro == "state") {

View File

@ -39,9 +39,6 @@ public:
static ServiceState StateFromString(const String& state);
static String StateToString(ServiceState state);
static StateType StateTypeFromString(const String& state);
static String StateTypeToString(StateType state);
static void EvaluateApplyRules(const Host::Ptr& host);
void OnAllConfigLoaded() override;

View File

@ -12,12 +12,13 @@ using namespace icinga;
REGISTER_TYPE(User);
void User::OnConfigLoaded()
User::User()
{
ObjectImpl<User>::OnConfigLoaded();
SetTypeFilter(FilterArrayToInt(GetTypes(), Notification::GetTypeFilterMap(), ~0));
SetStateFilter(FilterArrayToInt(GetStates(), Notification::GetStateFilterMap(), ~0));
// If a User is created without specifying the "types/states" attribute, the Set* methods won't be called,
// consequently the filter bitset will also be 0. Thus, we need to ensure that the type/state filter are
// initialized to the default values, which are all types and states enabled.
SetTypes(nullptr, false, Empty);
SetStates(nullptr, false, Empty);
}
void User::OnAllConfigLoaded()
@ -80,6 +81,36 @@ TimePeriod::Ptr User::GetPeriod() const
return TimePeriod::GetByName(GetPeriodRaw());
}
Array::Ptr User::GetTypes() const
{
return m_Types.load();
}
void User::SetTypes(const Array::Ptr& value, bool suppress_events, const Value& cookie)
{
m_Types.store(value);
// Ensure that the type filter is updated when the types attribute changes.
SetTypeFilter(FilterArrayToInt(value, Notification::GetTypeFilterMap(), ~0));
if (!suppress_events) {
NotifyTypes(cookie);
}
}
Array::Ptr User::GetStates() const
{
return m_States.load();
}
void User::SetStates(const Array::Ptr& value, bool suppress_events, const Value& cookie)
{
m_States.store(value);
// Ensure that the state filter is updated when the states attribute changes.
SetStateFilter(FilterArrayToInt(value, Notification::GetStateFilterMap(), ~0));
if (!suppress_events) {
NotifyStates(cookie);
}
}
void User::ValidateStates(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils)
{
ObjectImpl<User>::ValidateStates(lvalue, utils);

View File

@ -22,21 +22,38 @@ public:
DECLARE_OBJECT(User);
DECLARE_OBJECTNAME(User);
User();
void AddGroup(const String& name);
/* Notifications */
TimePeriod::Ptr GetPeriod() const;
Array::Ptr GetTypes() const override;
void SetTypes(const Array::Ptr& value, bool suppress_events, const Value& cookie) override;
Array::Ptr GetStates() const override;
void SetStates(const Array::Ptr& value, bool suppress_events, const Value& cookie) override;
void ValidateStates(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils) override;
void ValidateTypes(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils) override;
protected:
void Stop(bool runtimeRemoved) override;
void OnConfigLoaded() override;
void OnAllConfigLoaded() override;
private:
mutable std::mutex m_UserMutex;
// These attributes represent the actual User "types" and "states" attributes from the "user.ti".
// However, since we want to ensure that the type and state bitsets are always in sync with those attributes,
// we need to override their setters, and this on the hand introduces another problem: The virtual setters are
// called from within the ObjectImpl<User> constructor, which obviously violates the C++ standard [^1].
// So, in order to avoid al this kind of mess, these two attributes have the "no_storage" flag set, and
// their getters/setters are pure virtual, which means this class has to provide the implementation of them.
//
// [^1]: https://isocpp.org/wiki/faq/strange-inheritance#calling-virtuals-from-ctors
AtomicOrLocked<Array::Ptr> m_Types;
AtomicOrLocked<Array::Ptr> m_States;
};
}

View File

@ -29,9 +29,15 @@ class User : CustomVarObject
}}}
};
[config] array(Value) types;
[config, no_storage] array(Value) types {
get;
set;
};
[no_user_view, no_user_modify] int type_filter_real (TypeFilter);
[config] array(Value) states;
[config, no_storage] array(Value) states {
get;
set;
};
[no_user_view, no_user_modify] int state_filter_real (StateFilter);
[config] String email;

View File

@ -1624,8 +1624,8 @@ bool IcingaDB::PrepareObject(const ConfigObject::Ptr& object, Dictionary::Ptr& a
attributes->Set("email", user->GetEmail());
attributes->Set("pager", user->GetPager());
attributes->Set("notifications_enabled", user->GetEnableNotifications());
attributes->Set("states", user->GetStates());
attributes->Set("types", user->GetTypes());
attributes->Set("states", StateFilterToRedisValue(user->GetStateFilter()));
attributes->Set("types", TypeFilterToRedisValue(user->GetTypeFilter()));
if (user->GetPeriod())
attributes->Set("timeperiod_id", GetObjectIdentifier(user->GetPeriod()));
@ -1673,8 +1673,8 @@ bool IcingaDB::PrepareObject(const ConfigObject::Ptr& object, Dictionary::Ptr& a
}
attributes->Set("notification_interval", std::max(0.0, std::round(notification->GetInterval())));
attributes->Set("states", notification->GetStates());
attributes->Set("types", notification->GetTypes());
attributes->Set("states", StateFilterToRedisValue(notification->GetStateFilter()));
attributes->Set("types", TypeFilterToRedisValue(notification->GetTypeFilter()));
return true;
}
@ -1684,7 +1684,7 @@ bool IcingaDB::PrepareObject(const ConfigObject::Ptr& object, Dictionary::Ptr& a
attributes->Set("author", comment->GetAuthor());
attributes->Set("text", comment->GetText());
attributes->Set("entry_type", comment->GetEntryType());
attributes->Set("entry_type", IcingaDB::CommentTypeToString(comment->GetEntryType()));
attributes->Set("entry_time", TimestampToMilliseconds(comment->GetEntryTime()));
attributes->Set("is_persistent", comment->GetPersistent());
attributes->Set("is_sticky", comment->GetSticky());
@ -1974,7 +1974,7 @@ void IcingaDB::SendStateChange(const ConfigObject::Ptr& object, const CheckResul
"id", HashValue(rawId),
"environment_id", m_EnvironmentId,
"host_id", GetObjectIdentifier(host),
"state_type", Convert::ToString(type),
"state_type", Checkable::StateTypeToString(type).ToLower(),
"soft_state", Convert::ToString(cr ? service ? Convert::ToLong(cr->GetState()) : Convert::ToLong(Host::CalculateState(cr->GetState())) : 99),
"hard_state", Convert::ToString(hard_state),
"check_attempt", Convert::ToString(checkable->GetCheckAttempt()),
@ -2047,8 +2047,9 @@ void IcingaDB::SendSentNotification(
auto usersAmount (users.size());
auto sendTs (TimestampToMilliseconds(sendTime));
auto notificationTypeStr(GetNotificationTypeByEnum(type));
Array::Ptr rawId = new Array({m_EnvironmentId, notification->GetName()});
rawId->Add(GetNotificationTypeByEnum(type));
rawId->Add(notificationTypeStr);
rawId->Add(sendTs);
auto notificationHistoryId (HashValue(rawId));
@ -2059,7 +2060,7 @@ void IcingaDB::SendSentNotification(
"environment_id", m_EnvironmentId,
"notification_id", GetObjectIdentifier(notification),
"host_id", GetObjectIdentifier(host),
"type", Convert::ToString(type),
"type", notificationTypeStr,
"state", Convert::ToString(cr ? service ? Convert::ToLong(cr->GetState()) : Convert::ToLong(Host::CalculateState(cr->GetState())) : 99),
"previous_hard_state", Convert::ToString(cr ? service ? Convert::ToLong(cr->GetPreviousHardState()) : Convert::ToLong(Host::CalculateState(cr->GetPreviousHardState())) : 99),
"author", Utility::ValidateUTF8(author),
@ -2300,7 +2301,7 @@ void IcingaDB::SendAddedComment(const Comment::Ptr& comment)
"entry_time", Convert::ToString(TimestampToMilliseconds(comment->GetEntryTime())),
"author", Utility::ValidateUTF8(comment->GetAuthor()),
"comment", Utility::ValidateUTF8(comment->GetText()),
"entry_type", Convert::ToString(comment->GetEntryType()),
"entry_type", IcingaDB::CommentTypeToString(comment->GetEntryType()),
"is_persistent", Convert::ToString((unsigned short)comment->GetPersistent()),
"is_sticky", Convert::ToString((unsigned short)comment->GetSticky()),
"event_id", CalcEventID("comment_add", comment),
@ -2372,7 +2373,7 @@ void IcingaDB::SendRemovedComment(const Comment::Ptr& comment)
"entry_time", Convert::ToString(TimestampToMilliseconds(comment->GetEntryTime())),
"author", Utility::ValidateUTF8(comment->GetAuthor()),
"comment", Utility::ValidateUTF8(comment->GetText()),
"entry_type", Convert::ToString(comment->GetEntryType()),
"entry_type", IcingaDB::CommentTypeToString(comment->GetEntryType()),
"is_persistent", Convert::ToString((unsigned short)comment->GetPersistent()),
"is_sticky", Convert::ToString((unsigned short)comment->GetSticky()),
"event_id", CalcEventID("comment_remove", comment),
@ -2954,7 +2955,7 @@ Dictionary::Ptr IcingaDB::SerializeState(const Checkable::Ptr& checkable)
*/
attrs->Set("id", id);
attrs->Set("environment_id", m_EnvironmentId);
attrs->Set("state_type", checkable->HasBeenChecked() ? checkable->GetStateType() : StateTypeHard);
attrs->Set("state_type", Checkable::StateTypeToString(checkable->HasBeenChecked() ? checkable->GetStateType() : StateTypeHard).ToLower());
// TODO: last_hard/soft_state should be "previous".
if (service) {
@ -3017,7 +3018,9 @@ Dictionary::Ptr IcingaDB::SerializeState(const Checkable::Ptr& checkable)
attrs->Set("is_reachable", checkable->IsReachable());
attrs->Set("is_flapping", checkable->IsFlapping());
attrs->Set("is_acknowledged", checkable->GetAcknowledgement());
attrs->Set("is_acknowledged", checkable->IsAcknowledged());
attrs->Set("is_sticky_acknowledgement", checkable->GetAcknowledgement() == AcknowledgementSticky);
if (checkable->IsAcknowledged()) {
Timestamp entry = 0;
Comment::Ptr AckComment;

View File

@ -219,6 +219,35 @@ Dictionary::Ptr IcingaDB::SerializeRedundancyGroupState(const Checkable::Ptr& ch
};
}
/**
* Converts the given filter to its Redis value representation.
*
* Within the Icinga 2 code base, if the states filter bitsets are set to -1, the filter will match on all states.
* However, since sending -1 to Redis would crash the Icinga DB daemon, as the "states" field is of type uint8, so
* the primary purpose of this function is to make sure that no values outside the valid range of 0-255 are sent to Redis.
*
* @param filter The filter to convert.
*/
int IcingaDB::StateFilterToRedisValue(int filter)
{
return filter & StateFilterAll;
}
/**
* Converts the given filter to its Redis value representation.
*
* Within the Icinga 2 code base, if the types filter bitsets are set to -1, the filter will match on all types.
* However, since sending -1 to Redis would crash the Icinga DB daemon, as the "types" field is of type uint16, so
* the primary purpose of this function is to make sure that no values outside the "types" field's valid range are
* sent to Redis.
*
* @param filter The filter to convert.
*/
int IcingaDB::TypeFilterToRedisValue(int filter)
{
return filter & NotificationTypeAll;
}
const char* IcingaDB::GetNotificationTypeByEnum(NotificationType type)
{
switch (type) {
@ -240,9 +269,24 @@ const char* IcingaDB::GetNotificationTypeByEnum(NotificationType type)
return "flapping_start";
case NotificationFlappingEnd:
return "flapping_end";
default:
VERIFY(!"Invalid notification type.");
}
}
VERIFY(!"Invalid notification type.");
/**
* Converts the given comment type to its string representation.
*
* @ingroup icinga
*/
String IcingaDB::CommentTypeToString(CommentType type)
{
switch (type) {
case CommentUser: return "comment";
case CommentAcknowledgement: return "ack";
default:
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid comment type specified"));
}
}
static const std::set<String> propertiesBlacklistEmpty;

View File

@ -173,7 +173,10 @@ private:
static String GetObjectIdentifier(const ConfigObject::Ptr& object);
static String CalcEventID(const char* eventType, const ConfigObject::Ptr& object, double eventTime = 0, NotificationType nt = NotificationType(0));
static int StateFilterToRedisValue(int filter);
static int TypeFilterToRedisValue(int filter);
static const char* GetNotificationTypeByEnum(NotificationType type);
static String CommentTypeToString(CommentType type);
static Dictionary::Ptr SerializeVars(const Dictionary::Ptr& vars);
static Dictionary::Ptr SerializeDependencyEdgeState(const DependencyGroup::Ptr& dependencyGroup, const Dependency::Ptr& dep);
static Dictionary::Ptr SerializeRedundancyGroupState(const Checkable::Ptr& child, const DependencyGroup::Ptr& redundancyGroup);