-
Notifications
You must be signed in to change notification settings - Fork 586
IcingaDB: Make Redis & DB values consistent #10452
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
So that Icinga DB (Go) daemon doesn't have to make the mappings again.
…trs change Since the types and states attributes are user configurable and allowed to change at runtime, we need to hook into the `OnTypesChanged` and `OnStatesChanged` signals to update the actual filter bitsets whenever these attributes change. Otherwise, the filter bitsets would be stale and not reflect their current state.
/** | ||
* Converts the given filter to its Redis value representation. | ||
* | ||
* Within the Icinga 2 code base, if the states and types filter bitsets are set to -1, the filter will match | ||
* on all states/types. However, since sending -1 to Redis would crash the Icinga DB daemon, as both fields are | ||
* of type Go's uint8/uint16, so the primary purpose of this function is to make sure that no negative values are | ||
* passed to Redis. | ||
* | ||
* @param filter The filter to convert. | ||
* @param isStatesFilter Whether the given filter is a states filter or a types filter. | ||
*/ | ||
int IcingaDB::StatesOrTypesFilterToRedisValue(int filter, bool isStatesFilter) | ||
{ | ||
if (filter >= 0) { | ||
return filter; | ||
} | ||
|
||
if (isStatesFilter) { | ||
return StateFilterOK | StateFilterWarning | StateFilterCritical | StateFilterUnknown | StateFilterUp | StateFilterDown; | ||
} | ||
|
||
return NotificationDowntimeStart | NotificationDowntimeEnd | NotificationDowntimeRemoved | NotificationCustom | | ||
NotificationAcknowledgement | NotificationProblem | NotificationRecovery | NotificationFlappingStart | NotificationFlappingEnd; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this function odd for two reasons:
- It repeats all the
StateFilter*
andNotification*
values. - The "what does it actually do" bool argument makes it's uses somewhat awkward:
icinga2/lib/icingadb/icingadb-objects.cpp
Lines 1627 to 1628 in 98dee18
attributes->Set("states", StatesOrTypesFilterToRedisValue(user->GetStateFilter(), true)); attributes->Set("types", StatesOrTypesFilterToRedisValue(user->GetTypeFilter(), false));
Those would be more readable if this was just two different functions:attributes->Set("states", StateFilterToRedisValue(user->GetStateFilter())); attributes->Set("types", TypeFilterToRedisValue(user->GetTypeFilter()));
Where does that -1
actually come from? That's what you get from using ~0
(i.e. all bits set to 1) as an integer as it's used here for example:
Lines 19 to 20 in c253e7e
SetTypeFilter(FilterArrayToInt(GetTypes(), Notification::GetTypeFilterMap(), ~0)); | |
SetStateFilter(FilterArrayToInt(GetStates(), Notification::GetStateFilterMap(), ~0)); |
Given that FilterArrayToInt()
already receives all valid values in filterMap
, it could also restrict its return value to an integer with only the bits set that are actually used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that
FilterArrayToInt()
already receives all valid values infilterMap
, it could also restrict its return value to an integer with only the bits set that are actually used.
That would break this validation! Yes, it's weird, but the -1
value has currently two different interpretations.
Lines 87 to 90 in c253e7e
int filter = FilterArrayToInt(lvalue(), Notification::GetStateFilterMap(), 0); | |
if (filter == -1 || (filter & ~(StateFilterUp | StateFilterDown | StateFilterOK | StateFilterWarning | StateFilterCritical | StateFilterUnknown)) != 0) | |
BOOST_THROW_EXCEPTION(ValidationError(this, { "states" }, "State filter is invalid.")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or were you referring with your comment to its callers to provide all bits of the desired filters instead of ~0
and leave the implementation of FilterArrayToInt()
unchanged?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wow, that's a great example why C-style error reporting sucks.
I was thinking of changing FilterArrayToInt()
but didn't look into it for long enough that it also returns -1
to report errors. However, that shouldn't be a problem for what I had in mind: in the if (!typeFilters)
, just unset all bits from defaultValue
that are not set in any filterMap
value.
Untested diff of what I had in mind
diff --git a/lib/icinga/customvarobject.cpp b/lib/icinga/customvarobject.cpp
index 0edc369d9..d50b8f49d 100644
--- a/lib/icinga/customvarobject.cpp
+++ b/lib/icinga/customvarobject.cpp
@@ -19,12 +19,16 @@ void CustomVarObject::ValidateVars(const Lazy<Dictionary::Ptr>& lvalue, const Va
int icinga::FilterArrayToInt(const Array::Ptr& typeFilters, const std::map<String, int>& filterMap, int defaultValue)
{
- int resultTypeFilter;
+ int resultTypeFilter = 0;
- if (!typeFilters)
- return defaultValue;
-
- resultTypeFilter = 0;
+ if (!typeFilters) {
+ for (const auto & [name, value] : filterMap) {
+ if (defaultValue & value) {
+ resultTypeFilter |= value;
+ }
+ }
+ return resultTypeFilter;
+ }
ObjectLock olock(typeFilters);
for (const Value& typeFilter : typeFilters) {
When additionally performing some sanitization in if (typeFilter.IsNumber())
, that function would then return -1 to report errors (that error reporting mechanism could be improved though) or a non-negative value where only valid bits are set (valid bits in this case is defined as all bits that are set in any filterMap
value).
Given that FilterArrayToInt()
seems to be called only with defaultValue = 0
or defaultValue = ~0
, there could be room for some simplification by just giving a boolean with the default for all valid bits.
// Since the types and states attributes are user configurable and allowed to change at runtime, we need to | ||
// hook into the OnTypesChanged and OnStatesChanged signals to update the actual filter bitsets whenever these | ||
// attributes change. Otherwise, the filter bitsets would be stale and not reflect their current state. | ||
OnTypesChanged.connect([](const Notification::Ptr& notification, const MessageOrigin::Ptr&) { | ||
notification->SetTypeFilter(FilterArrayToInt(notification->GetTypes(), GetTypeFilterMap(), ~0)); | ||
}); | ||
OnStatesChanged.connect([](const Notification::Ptr& notification, const MessageOrigin::Ptr&) { | ||
notification->SetStateFilter(FilterArrayToInt(notification->GetStates(), GetStateFilterMap(), ~0)); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't this simply be done as part of the SetTypes()
and SetStates()
implementation? I don't see why this should need the indirection through these signals.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't this simply be done as part of the
SetTypes()
andSetStates()
implementation?
Of course, it can be implemented that way but simply isn't the word that would describe it. I just found it easier to use their existing signals instead of having to mess around with the class compiler (making the setters virtual, so that they can be overridden in the User
and Notification
class accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mess around with the class compiler
As in using it or changing something within its implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As in using it or changing something within its implementation?
It doesn't matter now, already changed it and marked the two attributes as set_virtual
in the dubious {user,notfication}.ti
files.
This tries to make some of the values written to Redis and to the Database more consistent. Specifically, previously Icinga DB sent 0 or 1 for the
state_type
field, and the Icinga DB (Go) daemon mapped them tosoft
andhard
accordingly before writing them to the database. This is not the case anymore, and Icinga DB (C++) now writessoft
andhard
directly to Redis. This also applies to thecomment#entry_type
,{user, notification}#types,states
andnotification#type
fields. They all are now serialized exactly how they're going to be written to the database, thus eliminating the need for the Go daemon to do any mappings. Apart from that, theis_acknowledged
field is now serialized astrue
orfalse
as opposed to1
(normal) or2
(sticky) previously. From now on that field will be set totrue
if the checkable has been acknowledged, and the newis_sticky_acknowledgement
field will be set totrue
if the acknowledgement is sticky.One additional bug that's been fixed by this PR but isn't related to the above topic is that the
{user, notification}#types,states
bitsets will correctly be refreshed whenever the user or notificationtypes,states
attributes are changed at runtime. Previously, the bitsets were only updated at startup, and never refreshed again (see 98dee18).Required By
fixes #9427