Merge pull request #9009 from Icinga/bugfix/icingadb-runtime-updates-delete-relationships

Icinga DB: Make sure object relationships are handled correctly during runtime updates
This commit is contained in:
Noah Hilverling 2021-11-12 17:52:59 +01:00 committed by GitHub
commit 4d3b1709fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 521 additions and 73 deletions

View File

@ -235,10 +235,10 @@ Object::Ptr Dictionary::Clone() const
}
/**
* Returns an array containing all keys
* Returns an ordered vector containing all keys
* which are currently set in this directory.
*
* @returns an array of key names
* @returns an ordered vector of key names
*/
std::vector<String> Dictionary::GetKeys() const
{

View File

@ -11,11 +11,11 @@ namespace icinga
abstract class Command : CustomVarObject
{
[config] Value command (CommandLine);
[config] Value arguments;
[config, signal_with_old_value] Value arguments;
[config] int timeout {
default {{{ return 60; }}}
};
[config] Dictionary::Ptr env;
[config, signal_with_old_value] Dictionary::Ptr env;
[config, required] Function::Ptr execute;
};

View File

@ -9,7 +9,7 @@ namespace icinga
abstract class CustomVarObject : ConfigObject
{
[config] Dictionary::Ptr vars;
[config, signal_with_old_value] Dictionary::Ptr vars;
};
}

View File

@ -15,7 +15,7 @@ class Host : Checkable
load_after Endpoint;
load_after Zone;
[config, no_user_modify, required] array(name(HostGroup)) groups {
[config, no_user_modify, required, signal_with_old_value] array(name(HostGroup)) groups {
default {{{ return new Array(); }}}
};

View File

@ -133,6 +133,9 @@ void Notification::Start(bool runtimeCreated)
if (ApiListener::IsHACluster() && GetNextNotification() < Utility::GetTime() + 60)
SetNextNotification(Utility::GetTime() + 60, true);
for (const UserGroup::Ptr& group : GetUserGroups())
group->AddNotification(this);
ObjectImpl<Notification>::Start(runtimeCreated);
}
@ -144,6 +147,9 @@ void Notification::Stop(bool runtimeRemoved)
if (obj)
obj->UnregisterNotification(this);
for (const UserGroup::Ptr& group : GetUserGroups())
group->RemoveNotification(this);
}
Checkable::Ptr Notification::GetCheckable() const

View File

@ -36,8 +36,8 @@ class Notification : CustomVarObject < NotificationNameComposer
return TimePeriod::GetByName(GetPeriodRaw());
}}}
};
[config, protected] array(name(User)) users (UsersRaw);
[config, protected] array(name(UserGroup)) user_groups (UserGroupsRaw);
[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;
[no_user_view, no_user_modify] int type_filter_real (TypeFilter);

View File

@ -27,7 +27,7 @@ class Service : Checkable < ServiceNameComposer
load_after Host;
load_after Zone;
[config, no_user_modify, required] array(name(ServiceGroup)) groups {
[config, no_user_modify, required, signal_with_old_value] array(name(ServiceGroup)) groups {
default {{{ return new Array(); }}}
};

View File

@ -18,15 +18,15 @@ class TimePeriod : CustomVarObject
return m_DisplayName.m_Value;
}}}
};
[config] Dictionary::Ptr ranges;
[config, signal_with_old_value] Dictionary::Ptr ranges;
[config, required] Function::Ptr update;
[config] bool prefer_includes {
default {{{ return true; }}}
};
[config, required] array(name(TimePeriod)) excludes {
[config, required, signal_with_old_value] array(name(TimePeriod)) excludes {
default {{{ return new Array(); }}}
};
[config, required] array(name(TimePeriod)) includes {
[config, required, signal_with_old_value] array(name(TimePeriod)) includes {
default {{{ return new Array(); }}}
};
[state, no_user_modify] Value valid_begin;

View File

@ -19,7 +19,7 @@ class User : CustomVarObject
return m_DisplayName.m_Value;
}}}
};
[config, no_user_modify, required] array(name(UserGroup)) groups {
[config, no_user_modify, required, signal_with_old_value] array(name(UserGroup)) groups {
default {{{ return new Array(); }}}
};
[config, navigation] name(TimePeriod) period (PeriodRaw) {

View File

@ -76,6 +76,24 @@ void UserGroup::RemoveMember(const User::Ptr& user)
m_Members.erase(user);
}
std::set<Notification::Ptr> UserGroup::GetNotifications() const
{
std::unique_lock<std::mutex> lock(m_UserGroupMutex);
return m_Notifications;
}
void UserGroup::AddNotification(const Notification::Ptr& notification)
{
std::unique_lock<std::mutex> lock(m_UserGroupMutex);
m_Notifications.insert(notification);
}
void UserGroup::RemoveNotification(const Notification::Ptr& notification)
{
std::unique_lock<std::mutex> lock(m_UserGroupMutex);
m_Notifications.erase(notification);
}
bool UserGroup::ResolveGroupMembership(const User::Ptr& user, bool add, int rstack) {
if (add && rstack > 20) {

View File

@ -11,6 +11,7 @@ namespace icinga
{
class ConfigItem;
class Notification;
/**
* An Icinga user group.
@ -27,6 +28,10 @@ public:
void AddMember(const User::Ptr& user);
void RemoveMember(const User::Ptr& user);
std::set<intrusive_ptr<Notification>> GetNotifications() const;
void AddNotification(const intrusive_ptr<Notification>& notification);
void RemoveNotification(const intrusive_ptr<Notification>& notification);
bool ResolveGroupMembership(const User::Ptr& user, bool add = true, int rstack = 0);
static void EvaluateObjectRules(const User::Ptr& user);
@ -34,6 +39,7 @@ public:
private:
mutable std::mutex m_UserGroupMutex;
std::set<User::Ptr> m_Members;
std::set<intrusive_ptr<Notification>> m_Notifications;
static bool EvaluateObjectRule(const User::Ptr& user, const intrusive_ptr<ConfigItem>& group);
};

View File

@ -15,6 +15,7 @@
#include "base/array.hpp"
#include "base/exception.hpp"
#include "base/utility.hpp"
#include "base/object-packer.hpp"
#include "icinga/command.hpp"
#include "icinga/compatutility.hpp"
#include "icinga/customvarobject.hpp"
@ -35,6 +36,7 @@
#include <mutex>
#include <set>
#include <utility>
#include <type_traits>
using namespace icinga;
@ -127,6 +129,40 @@ void IcingaDB::ConfigStaticInitialize()
Service::OnHostProblemChanged.connect([](const Service::Ptr& service, const CheckResult::Ptr&, const MessageOrigin::Ptr&) {
IcingaDB::StateChangeHandler(service);
});
Notification::OnUsersRawChangedWithOldValue.connect([](const Notification::Ptr& notification, const Value& oldValues, const Value& newValues) {
IcingaDB::NotificationUsersChangedHandler(notification, oldValues, newValues);
});
Notification::OnUserGroupsRawChangedWithOldValue.connect([](const Notification::Ptr& notification, const Value& oldValues, const Value& newValues) {
IcingaDB::NotificationUserGroupsChangedHandler(notification, oldValues, newValues);
});
TimePeriod::OnRangesChangedWithOldValue.connect([](const TimePeriod::Ptr& timeperiod, const Value& oldValues, const Value& newValues) {
IcingaDB::TimePeriodRangesChangedHandler(timeperiod, oldValues, newValues);
});
TimePeriod::OnIncludesChangedWithOldValue.connect([](const TimePeriod::Ptr& timeperiod, const Value& oldValues, const Value& newValues) {
IcingaDB::TimePeriodIncludesChangedHandler(timeperiod, oldValues, newValues);
});
TimePeriod::OnExcludesChangedWithOldValue.connect([](const TimePeriod::Ptr& timeperiod, const Value& oldValues, const Value& newValues) {
IcingaDB::TimePeriodExcludesChangedHandler(timeperiod, oldValues, newValues);
});
User::OnGroupsChangedWithOldValue.connect([](const User::Ptr& user, const Value& oldValues, const Value& newValues) {
IcingaDB::UserGroupsChangedHandler(user, oldValues, newValues);
});
Host::OnGroupsChangedWithOldValue.connect([](const Host::Ptr& host, const Value& oldValues, const Value& newValues) {
IcingaDB::HostGroupsChangedHandler(host, oldValues, newValues);
});
Service::OnGroupsChangedWithOldValue.connect([](const Service::Ptr& service, const Value& oldValues, const Value& newValues) {
IcingaDB::ServiceGroupsChangedHandler(service, oldValues, newValues);
});
Command::OnEnvChangedWithOldValue.connect([](const ConfigObject::Ptr& command, const Value& oldValues, const Value& newValues) {
IcingaDB::CommandEnvChangedHandler(command, oldValues, newValues);
});
Command::OnArgumentsChangedWithOldValue.connect([](const ConfigObject::Ptr& command, const Value& oldValues, const Value& newValues) {
IcingaDB::CommandArgumentsChangedHandler(command, oldValues, newValues);
});
CustomVarObject::OnVarsChangedWithOldValue.connect([](const ConfigObject::Ptr& object, const Value& oldValues, const Value& newValues) {
IcingaDB::CustomVarsChangedHandler(object, oldValues, newValues);
});
}
void IcingaDB::UpdateAllConfigObjects()
@ -609,7 +645,7 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
CustomVarObject::Ptr customVarObject = dynamic_pointer_cast<CustomVarObject>(object);
if (customVarObject) {
auto vars(SerializeVars(customVarObject));
auto vars(SerializeVars(customVarObject->GetVars()));
if (vars) {
auto& typeCvs (hMSets[m_PrefixConfigObject + typeName + ":customvar"]);
auto& allCvs (hMSets[m_PrefixConfigObject + "customvar"]);
@ -825,12 +861,7 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
if (type == User::TypeInstance) {
User::Ptr user = static_pointer_cast<User>(object);
Array::Ptr groups;
ConfigObject::Ptr (*getGroup)(const String& name);
groups = user->GetGroups();
getGroup = &::GetObjectByName<UserGroup>;
Array::Ptr groups = user->GetGroups();
if (groups) {
ObjectLock groupsLock(groups);
@ -839,9 +870,10 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
groupIds->Reserve(groups->GetLength());
auto& members (hMSets[m_PrefixConfigObject + typeName + "group:member"]);
auto& notificationRecipients (hMSets[m_PrefixConfigObject + "notification:recipient"]);
for (auto& group : groups) {
auto groupObj ((*getGroup)(group));
UserGroup::Ptr groupObj = UserGroup::GetByName(group);
String groupId = GetObjectIdentifier(groupObj);
String id = HashValue(new Array({m_EnvironmentId, groupObj->GetName(), object->GetName()}));
members.emplace_back(id);
@ -850,6 +882,16 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
if (runtimeUpdate) {
AddObjectDataToRuntimeUpdates(runtimeUpdates, id, m_PrefixConfigObject + typeName + "group:member", data);
// Recipients are handled by notifications during initial dumps and only need to be handled here during runtime (e.g. User creation).
for (auto& notification : groupObj->GetNotifications()) {
String recipientId = HashValue(new Array({m_EnvironmentId, "usergroupuser", user->GetName(), groupObj->GetName(), notification->GetName()}));
notificationRecipients.emplace_back(recipientId);
Dictionary::Ptr recipientData = new Dictionary({{"notification_id", GetObjectIdentifier(notification)}, {"environment_id", m_EnvironmentId}, {"user_id", objectKey}, {"usergroup_id", groupId}});
notificationRecipients.emplace_back(JsonEncode(recipientData));
AddObjectDataToRuntimeUpdates(runtimeUpdates, recipientId, m_PrefixConfigObject + "notification:recipient", recipientData);
}
}
groupIds->Add(groupId);
@ -863,10 +905,6 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
Notification::Ptr notification = static_pointer_cast<Notification>(object);
std::set<User::Ptr> users = notification->GetUsers();
std::set<User::Ptr> allUsers;
std::copy(users.begin(), users.end(), std::inserter(allUsers, allUsers.begin()));
Array::Ptr userIds = new Array();
auto usergroups(notification->GetUserGroups());
@ -875,16 +913,22 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
userIds->Reserve(users.size());
auto& usrs (hMSets[m_PrefixConfigObject + typeName + ":user"]);
auto& notificationRecipients (hMSets[m_PrefixConfigObject + typeName + ":recipient"]);
for (auto& user : users) {
String userId = GetObjectIdentifier(user);
String id = HashValue(new Array({m_EnvironmentId, user->GetName(), object->GetName()}));
String id = HashValue(new Array({m_EnvironmentId, "user", user->GetName(), object->GetName()}));
usrs.emplace_back(id);
notificationRecipients.emplace_back(id);
Dictionary::Ptr data = new Dictionary({{"notification_id", objectKey}, {"environment_id", m_EnvironmentId}, {"user_id", userId}});
usrs.emplace_back(JsonEncode(data));
String dataJson = JsonEncode(data);
usrs.emplace_back(dataJson);
notificationRecipients.emplace_back(dataJson);
if (runtimeUpdate) {
AddObjectDataToRuntimeUpdates(runtimeUpdates, id, m_PrefixConfigObject + typeName + ":user", data);
AddObjectDataToRuntimeUpdates(runtimeUpdates, id, m_PrefixConfigObject + typeName + ":recipient", data);
}
userIds->Add(userId);
@ -893,43 +937,38 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
usergroupIds->Reserve(usergroups.size());
auto& groups (hMSets[m_PrefixConfigObject + typeName + ":usergroup"]);
auto& notificationRecipients (hMSets[m_PrefixConfigObject + typeName + ":recipient"]);
for (auto& usergroup : usergroups) {
String usergroupId = GetObjectIdentifier(usergroup);
auto groupMembers = usergroup->GetMembers();
std::copy(groupMembers.begin(), groupMembers.end(), std::inserter(allUsers, allUsers.begin()));
String id = HashValue(new Array({m_EnvironmentId, "usergroup", usergroup->GetName(), object->GetName()}));
groups.emplace_back(id);
Dictionary::Ptr groupData = new Dictionary({{"notification_id", objectKey}, {"environment_id", m_EnvironmentId}, {"usergroup_id", usergroupId}});
groups.emplace_back(JsonEncode(groupData));
notificationRecipients.emplace_back(id);
Dictionary::Ptr notificationRecipientData = new Dictionary({{"notification_id", objectKey}, {"environment_id", m_EnvironmentId}, {"usergroup_id", usergroupId}});
notificationRecipients.emplace_back(JsonEncode(notificationRecipientData));
Dictionary::Ptr groupData = new Dictionary({{"notification_id", objectKey}, {"environment_id", m_EnvironmentId}, {"usergroup_id", usergroupId}});
String groupDataJson = JsonEncode(groupData);
groups.emplace_back(groupDataJson);
notificationRecipients.emplace_back(groupDataJson);
if (runtimeUpdate) {
AddObjectDataToRuntimeUpdates(runtimeUpdates, id, m_PrefixConfigObject + typeName + ":usergroup", groupData);
AddObjectDataToRuntimeUpdates(runtimeUpdates, id, m_PrefixConfigObject + typeName + ":recipient", notificationRecipientData);
AddObjectDataToRuntimeUpdates(runtimeUpdates, id, m_PrefixConfigObject + typeName + ":recipient", groupData);
}
for (const User::Ptr& user : usergroup->GetMembers()) {
String userId = GetObjectIdentifier(user);
String recipientId = HashValue(new Array({m_EnvironmentId, "usergroupuser", user->GetName(), usergroup->GetName(), notification->GetName()}));
notificationRecipients.emplace_back(recipientId);
Dictionary::Ptr userData = new Dictionary({{"notification_id", objectKey}, {"environment_id", m_EnvironmentId}, {"user_id", userId}, {"usergroup_id", usergroupId}});
notificationRecipients.emplace_back(JsonEncode(userData));
if (runtimeUpdate) {
AddObjectDataToRuntimeUpdates(runtimeUpdates, recipientId, m_PrefixConfigObject + typeName + ":recipient", userData);
}
}
usergroupIds->Add(usergroupId);
}
for (auto& user : allUsers) {
String userId = GetObjectIdentifier(user);
String id = HashValue(new Array({m_EnvironmentId, "user", user->GetName(), object->GetName()}));
notificationRecipients.emplace_back(id);
Dictionary::Ptr data = new Dictionary({{"notification_id", objectKey}, {"environment_id", m_EnvironmentId}, {"user_id", userId}});
notificationRecipients.emplace_back(JsonEncode(data));
if (runtimeUpdate) {
AddObjectDataToRuntimeUpdates(runtimeUpdates, id, m_PrefixConfigObject + typeName + ":recipient", data);
}
}
return;
}
@ -1121,10 +1160,11 @@ void IcingaDB::SendConfigUpdate(const ConfigObject::Ptr& object, bool runtimeUpd
void IcingaDB::AddObjectDataToRuntimeUpdates(std::vector<Dictionary::Ptr>& runtimeUpdates, const String& objectKey,
const String& redisKey, const Dictionary::Ptr& data)
{
data->Set("id", objectKey);
data->Set("redis_key", redisKey);
data->Set("runtime_type", "upsert");
runtimeUpdates.emplace_back(data);
Dictionary::Ptr dataClone = data->ShallowClone();
dataClone->Set("id", objectKey);
dataClone->Set("redis_key", redisKey);
dataClone->Set("runtime_type", "upsert");
runtimeUpdates.emplace_back(dataClone);
}
// Takes object and collects IcingaDB relevant attributes and computes checksums. Returns whether the object is relevant
@ -1456,7 +1496,8 @@ IcingaDB::CreateConfigUpdate(const ConfigObject::Ptr& object, const String typeN
void IcingaDB::SendConfigDelete(const ConfigObject::Ptr& object)
{
String typeName = object->GetReflectionType()->GetName().ToLower();
Type::Ptr type = object->GetReflectionType();
String typeName = type->GetName().ToLower();
String objectKey = GetObjectIdentifier(object);
m_Rcon->FireAndForgetQueries({
@ -1468,12 +1509,23 @@ void IcingaDB::SendConfigDelete(const ConfigObject::Ptr& object)
}
}, Prio::Config);
auto checkable (dynamic_pointer_cast<Checkable>(object));
CustomVarObject::Ptr customVarObject = dynamic_pointer_cast<CustomVarObject>(object);
if (customVarObject) {
Dictionary::Ptr vars = customVarObject->GetVars();
SendCustomVarsChanged(object, vars, nullptr);
}
if (type == Host::TypeInstance || type == Service::TypeInstance) {
Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
Host::Ptr host;
Service::Ptr service;
tie(host, service) = GetHostService(checkable);
if (checkable) {
m_Rcon->FireAndForgetQuery({
"ZREM",
dynamic_pointer_cast<Service>(checkable) ? "icinga:nextupdate:service" : "icinga:nextupdate:host",
service ? "icinga:nextupdate:service" : "icinga:nextupdate:host",
GetObjectIdentifier(checkable)
}, Prio::CheckResult);
@ -1481,6 +1533,42 @@ void IcingaDB::SendConfigDelete(const ConfigObject::Ptr& object)
{"HDEL", m_PrefixConfigObject + typeName + ":state", objectKey},
{"HDEL", m_PrefixConfigCheckSum + typeName + ":state", objectKey}
}, Prio::RuntimeStateSync);
if (service) {
SendGroupsChanged<ServiceGroup>(checkable, service->GetGroups(), nullptr);
} else {
SendGroupsChanged<HostGroup>(checkable, host->GetGroups(), nullptr);
}
return;
}
if (type == TimePeriod::TypeInstance) {
TimePeriod::Ptr timeperiod = static_pointer_cast<TimePeriod>(object);
SendTimePeriodRangesChanged(timeperiod, timeperiod->GetRanges(), nullptr);
SendTimePeriodIncludesChanged(timeperiod, timeperiod->GetIncludes(), nullptr);
SendTimePeriodExcludesChanged(timeperiod, timeperiod->GetExcludes(), nullptr);
return;
}
if (type == User::TypeInstance) {
User::Ptr user = static_pointer_cast<User>(object);
SendGroupsChanged<UserGroup>(user, user->GetGroups(), nullptr);
return;
}
if (type == Notification::TypeInstance) {
Notification::Ptr notification = static_pointer_cast<Notification>(object);
SendNotificationUsersChanged(notification, notification->GetUsersRaw(), nullptr);
SendNotificationUserGroupsChanged(notification, notification->GetUserGroupsRaw(), nullptr);
return;
}
if (type == CheckCommand::TypeInstance || type == NotificationCommand::TypeInstance || type == EventCommand::TypeInstance) {
Command::Ptr command = static_pointer_cast<Command>(object);
SendCommandArgumentsChanged(command, command->GetArguments(), nullptr);
SendCommandEnvChanged(command, command->GetEnv(), nullptr);
return;
}
}
@ -2174,6 +2262,150 @@ void IcingaDB::SendAcknowledgementCleared(const Checkable::Ptr& checkable, const
m_Rcon->FireAndForgetQuery(std::move(xAdd), Prio::History);
}
void IcingaDB::SendNotificationUsersChanged(const Notification::Ptr& notification, const Array::Ptr& oldValues, const Array::Ptr& newValues) {
if (!m_Rcon || !m_Rcon->IsConnected() || oldValues == newValues) {
return;
}
std::vector<Value> deletedUsers = GetArrayDeletedValues(oldValues, newValues);
for (const auto& userName : deletedUsers) {
String id = HashValue(new Array({m_EnvironmentId, "user", userName, notification->GetName()}));
DeleteRelationship(id, "notification:user");
DeleteRelationship(id, "notification:recipient");
}
}
void IcingaDB::SendNotificationUserGroupsChanged(const Notification::Ptr& notification, const Array::Ptr& oldValues, const Array::Ptr& newValues) {
if (!m_Rcon || !m_Rcon->IsConnected() || oldValues == newValues) {
return;
}
std::vector<Value> deletedUserGroups = GetArrayDeletedValues(oldValues, newValues);
for (const auto& userGroupName : deletedUserGroups) {
UserGroup::Ptr userGroup = UserGroup::GetByName(userGroupName);
String id = HashValue(new Array({m_EnvironmentId, "usergroup", userGroupName, notification->GetName()}));
DeleteRelationship(id, "notification:usergroup");
DeleteRelationship(id, "notification:recipient");
for (const User::Ptr& user : userGroup->GetMembers()) {
String userId = HashValue(new Array({m_EnvironmentId, "usergroupuser", user->GetName(), userGroupName, notification->GetName()}));
DeleteRelationship(userId, "notification:recipient");
}
}
}
void IcingaDB::SendTimePeriodRangesChanged(const TimePeriod::Ptr& timeperiod, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues) {
if (!m_Rcon || !m_Rcon->IsConnected() || oldValues == newValues) {
return;
}
std::vector<String> deletedKeys = GetDictionaryDeletedKeys(oldValues, newValues);
String typeName = GetLowerCaseTypeNameDB(timeperiod);
for (const auto& rangeKey : deletedKeys) {
String id = HashValue(new Array({m_EnvironmentId, rangeKey, oldValues->Get(rangeKey), timeperiod->GetName()}));
DeleteRelationship(id, "timeperiod:range");
}
}
void IcingaDB::SendTimePeriodIncludesChanged(const TimePeriod::Ptr& timeperiod, const Array::Ptr& oldValues, const Array::Ptr& newValues) {
if (!m_Rcon || !m_Rcon->IsConnected() || oldValues == newValues) {
return;
}
std::vector<Value> deletedIncludes = GetArrayDeletedValues(oldValues, newValues);
for (const auto& includeName : deletedIncludes) {
String id = HashValue(new Array({m_EnvironmentId, includeName, timeperiod->GetName()}));
DeleteRelationship(id, "timeperiod:override:include");
}
}
void IcingaDB::SendTimePeriodExcludesChanged(const TimePeriod::Ptr& timeperiod, const Array::Ptr& oldValues, const Array::Ptr& newValues) {
if (!m_Rcon || !m_Rcon->IsConnected() || oldValues == newValues) {
return;
}
std::vector<Value> deletedExcludes = GetArrayDeletedValues(oldValues, newValues);
for (const auto& excludeName : deletedExcludes) {
String id = HashValue(new Array({m_EnvironmentId, excludeName, timeperiod->GetName()}));
DeleteRelationship(id, "timeperiod:override:exclude");
}
}
template<typename T>
void IcingaDB::SendGroupsChanged(const ConfigObject::Ptr& object, const Array::Ptr& oldValues, const Array::Ptr& newValues) {
if (!m_Rcon || !m_Rcon->IsConnected() || oldValues == newValues) {
return;
}
std::vector<Value> deletedGroups = GetArrayDeletedValues(oldValues, newValues);
String typeName = GetLowerCaseTypeNameDB(object);
for (const auto& groupName : deletedGroups) {
typename T::Ptr group = ConfigObject::GetObject<T>(groupName);
String id = HashValue(new Array({m_EnvironmentId, group->GetName(), object->GetName()}));
DeleteRelationship(id, typeName + "group:member");
if (std::is_same<T, UserGroup>::value) {
UserGroup::Ptr userGroup = dynamic_pointer_cast<UserGroup>(group);
for (const auto& notification : userGroup->GetNotifications()) {
String userId = HashValue(new Array({m_EnvironmentId, "usergroupuser", object->GetName(), groupName, notification->GetName()}));
DeleteRelationship(userId, "notification:recipient");
}
}
}
}
void IcingaDB::SendCommandEnvChanged(const ConfigObject::Ptr& command, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues) {
if (!m_Rcon || !m_Rcon->IsConnected() || oldValues == newValues) {
return;
}
std::vector<String> deletedKeys = GetDictionaryDeletedKeys(oldValues, newValues);
String typeName = GetLowerCaseTypeNameDB(command);
for (const auto& envvarKey : deletedKeys) {
String id = HashValue(new Array({m_EnvironmentId, envvarKey, command->GetName()}));
DeleteRelationship(id, typeName + ":envvar", true);
}
}
void IcingaDB::SendCommandArgumentsChanged(const ConfigObject::Ptr& command, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues) {
if (!m_Rcon || !m_Rcon->IsConnected() || oldValues == newValues) {
return;
}
std::vector<String> deletedKeys = GetDictionaryDeletedKeys(oldValues, newValues);
String typeName = GetLowerCaseTypeNameDB(command);
for (const auto& argumentKey : deletedKeys) {
String id = HashValue(new Array({m_EnvironmentId, argumentKey, command->GetName()}));
DeleteRelationship(id, typeName + ":argument", true);
}
}
void IcingaDB::SendCustomVarsChanged(const ConfigObject::Ptr& object, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues) {
if (!m_Rcon || !m_Rcon->IsConnected() || oldValues == newValues) {
return;
}
Dictionary::Ptr oldVars = SerializeVars(oldValues);
Dictionary::Ptr newVars = SerializeVars(newValues);
std::vector<String> deletedVars = GetDictionaryDeletedKeys(oldVars, newVars);
String typeName = GetLowerCaseTypeNameDB(object);
for (const auto& varId : deletedVars) {
String id = HashValue(new Array({m_EnvironmentId, varId, object->GetName()}));
DeleteRelationship(id, typeName + ":customvar");
}
}
Dictionary::Ptr IcingaDB::SerializeState(const Checkable::Ptr& checkable)
{
Dictionary::Ptr attrs = new Dictionary();
@ -2473,3 +2705,89 @@ void IcingaDB::AcknowledgementClearedHandler(const Checkable::Ptr& checkable, co
}
}
}
void IcingaDB::NotificationUsersChangedHandler(const Notification::Ptr& notification, const Array::Ptr& oldValues, const Array::Ptr& newValues) {
for (const IcingaDB::Ptr& rw : ConfigType::GetObjectsByType<IcingaDB>()) {
rw->SendNotificationUsersChanged(notification, oldValues, newValues);
}
}
void IcingaDB::NotificationUserGroupsChangedHandler(const Notification::Ptr& notification, const Array::Ptr& oldValues, const Array::Ptr& newValues) {
for (const IcingaDB::Ptr& rw : ConfigType::GetObjectsByType<IcingaDB>()) {
rw->SendNotificationUserGroupsChanged(notification, oldValues, newValues);
}
}
void IcingaDB::TimePeriodRangesChangedHandler(const TimePeriod::Ptr& timeperiod, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues) {
for (const IcingaDB::Ptr& rw : ConfigType::GetObjectsByType<IcingaDB>()) {
rw->SendTimePeriodRangesChanged(timeperiod, oldValues, newValues);
}
}
void IcingaDB::TimePeriodIncludesChangedHandler(const TimePeriod::Ptr& timeperiod, const Array::Ptr& oldValues, const Array::Ptr& newValues) {
for (const IcingaDB::Ptr& rw : ConfigType::GetObjectsByType<IcingaDB>()) {
rw->SendTimePeriodIncludesChanged(timeperiod, oldValues, newValues);
}
}
void IcingaDB::TimePeriodExcludesChangedHandler(const TimePeriod::Ptr& timeperiod, const Array::Ptr& oldValues, const Array::Ptr& newValues) {
for (const IcingaDB::Ptr& rw : ConfigType::GetObjectsByType<IcingaDB>()) {
rw->SendTimePeriodExcludesChanged(timeperiod, oldValues, newValues);
}
}
void IcingaDB::UserGroupsChangedHandler(const User::Ptr& user, const Array::Ptr& oldValues, const Array::Ptr& newValues) {
for (const IcingaDB::Ptr& rw : ConfigType::GetObjectsByType<IcingaDB>()) {
rw->SendGroupsChanged<UserGroup>(user, oldValues, newValues);
}
}
void IcingaDB::HostGroupsChangedHandler(const Host::Ptr& host, const Array::Ptr& oldValues, const Array::Ptr& newValues) {
for (const IcingaDB::Ptr& rw : ConfigType::GetObjectsByType<IcingaDB>()) {
rw->SendGroupsChanged<HostGroup>(host, oldValues, newValues);
}
}
void IcingaDB::ServiceGroupsChangedHandler(const Service::Ptr& service, const Array::Ptr& oldValues, const Array::Ptr& newValues) {
for (const IcingaDB::Ptr& rw : ConfigType::GetObjectsByType<IcingaDB>()) {
rw->SendGroupsChanged<ServiceGroup>(service, oldValues, newValues);
}
}
void IcingaDB::CommandEnvChangedHandler(const ConfigObject::Ptr& command, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues) {
for (const IcingaDB::Ptr& rw : ConfigType::GetObjectsByType<IcingaDB>()) {
rw->SendCommandEnvChanged(command, oldValues, newValues);
}
}
void IcingaDB::CommandArgumentsChangedHandler(const ConfigObject::Ptr& command, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues) {
for (const IcingaDB::Ptr& rw : ConfigType::GetObjectsByType<IcingaDB>()) {
rw->SendCommandArgumentsChanged(command, oldValues, newValues);
}
}
void IcingaDB::CustomVarsChangedHandler(const ConfigObject::Ptr& object, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues) {
for (const IcingaDB::Ptr& rw : ConfigType::GetObjectsByType<IcingaDB>()) {
rw->SendCustomVarsChanged(object, oldValues, newValues);
}
}
void IcingaDB::DeleteRelationship(const String& id, const String& redisKeyWithoutPrefix, bool hasChecksum) {
Log(LogNotice, "IcingaDB") << "Deleting relationship '" << redisKeyWithoutPrefix << " -> '" << id << "'";
String redisKey = m_PrefixConfigObject + redisKeyWithoutPrefix;
std::vector<std::vector<String>> queries;
if (hasChecksum) {
queries.push_back({"HDEL", m_PrefixConfigCheckSum + redisKeyWithoutPrefix, id});
}
queries.push_back({"HDEL", redisKey, id});
queries.push_back({
"XADD", "icinga:runtime", "MAXLEN", "~", "1000000", "*",
"redis_key", redisKey, "id", id, "runtime_type", "delete"
});
m_Rcon->FireAndForgetQueries(queries, Prio::Config);
}

View File

@ -90,7 +90,7 @@ String IcingaDB::CalcEventID(const char* eventType, const ConfigObject::Ptr& obj
static const std::set<String> metadataWhitelist ({"package", "source_location", "templates"});
/**
* Prepare object's custom vars for being written to Redis
* Prepare custom vars for being written to Redis
*
* object.vars = {
* "disks": {
@ -124,14 +124,12 @@ static const std::set<String> metadataWhitelist ({"package", "source_location",
* }
* }
*
* @param object Config object with custom vars
* @param Dictionary Config object with custom vars
*
* @return JSON-like data structure for Redis
* @return JSON-like data structure for Redis
*/
Dictionary::Ptr IcingaDB::SerializeVars(const CustomVarObject::Ptr& object)
Dictionary::Ptr IcingaDB::SerializeVars(const Dictionary::Ptr& vars)
{
Dictionary::Ptr vars = object->GetVars();
if (!vars)
return nullptr;
@ -258,3 +256,49 @@ String IcingaDB::IcingaToStreamValue(const Value& value)
return JsonEncode(value);
}
}
// Returns the items that exist in "arrayOld" but not in "arrayNew"
std::vector<Value> IcingaDB::GetArrayDeletedValues(const Array::Ptr& arrayOld, const Array::Ptr& arrayNew) {
std::vector<Value> deletedValues;
if (!arrayOld) {
return deletedValues;
}
if (!arrayNew) {
return std::vector<Value>(arrayOld->Begin(), arrayOld->End());
}
std::vector<Value> vectorOld(arrayOld->Begin(), arrayOld->End());
std::sort(vectorOld.begin(), vectorOld.end());
vectorOld.erase(std::unique(vectorOld.begin(), vectorOld.end()), vectorOld.end());
std::vector<Value> vectorNew(arrayNew->Begin(), arrayNew->End());
std::sort(vectorNew.begin(), vectorNew.end());
vectorNew.erase(std::unique(vectorNew.begin(), vectorNew.end()), vectorNew.end());
std::set_difference(vectorOld.begin(), vectorOld.end(), vectorNew.begin(), vectorNew.end(), std::back_inserter(deletedValues));
return deletedValues;
}
// Returns the keys that exist in "dictOld" but not in "dictNew"
std::vector<String> IcingaDB::GetDictionaryDeletedKeys(const Dictionary::Ptr& dictOld, const Dictionary::Ptr& dictNew) {
std::vector<String> deletedKeys;
if (!dictOld) {
return deletedKeys;
}
std::vector<String> oldKeys = dictOld->GetKeys();
if (!dictNew) {
return oldKeys;
}
std::vector<String> newKeys = dictNew->GetKeys();
std::set_difference(oldKeys.begin(), oldKeys.end(), newKeys.begin(), newKeys.end(), std::back_inserter(deletedKeys));
return deletedKeys;
}

View File

@ -79,6 +79,7 @@ private:
void SendStateChange(const ConfigObject::Ptr& object, const CheckResult::Ptr& cr, StateType type);
void AddObjectDataToRuntimeUpdates(std::vector<Dictionary::Ptr>& runtimeUpdates, const String& objectKey,
const String& redisKey, const Dictionary::Ptr& data);
void DeleteRelationship(const String& id, const String& redisKeyWithoutPrefix, bool hasChecksum = false);
void SendSentNotification(
const Notification::Ptr& notification, const Checkable::Ptr& checkable, const std::set<User::Ptr>& users,
@ -93,6 +94,16 @@ private:
void SendNextUpdate(const Checkable::Ptr& checkable);
void SendAcknowledgementSet(const Checkable::Ptr& checkable, const String& author, const String& comment, AcknowledgementType type, bool persistent, double changeTime, double expiry);
void SendAcknowledgementCleared(const Checkable::Ptr& checkable, const String& removedBy, double changeTime, double ackLastChange);
void SendNotificationUsersChanged(const Notification::Ptr& notification, const Array::Ptr& oldValues, const Array::Ptr& newValues);
void SendNotificationUserGroupsChanged(const Notification::Ptr& notification, const Array::Ptr& oldValues, const Array::Ptr& newValues);
void SendTimePeriodRangesChanged(const TimePeriod::Ptr& timeperiod, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
void SendTimePeriodIncludesChanged(const TimePeriod::Ptr& timeperiod, const Array::Ptr& oldValues, const Array::Ptr& newValues);
void SendTimePeriodExcludesChanged(const TimePeriod::Ptr& timeperiod, const Array::Ptr& oldValues, const Array::Ptr& newValues);
template<class T>
void SendGroupsChanged(const ConfigObject::Ptr& command, const Array::Ptr& oldValues, const Array::Ptr& newValues);
void SendCommandEnvChanged(const ConfigObject::Ptr& command, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
void SendCommandArgumentsChanged(const ConfigObject::Ptr& command, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
void SendCustomVarsChanged(const ConfigObject::Ptr& object, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
std::vector<String> UpdateObjectAttrs(const ConfigObject::Ptr& object, int fieldType, const String& typeNameOverride);
Dictionary::Ptr SerializeState(const Checkable::Ptr& checkable);
@ -105,11 +116,13 @@ private:
static String FormatCommandLine(const Value& commandLine);
static long long TimestampToMilliseconds(double timestamp);
static String IcingaToStreamValue(const Value& value);
static std::vector<Value> GetArrayDeletedValues(const Array::Ptr& arrayOld, const Array::Ptr& arrayNew);
static std::vector<String> GetDictionaryDeletedKeys(const Dictionary::Ptr& dictOld, const Dictionary::Ptr& dictNew);
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 Dictionary::Ptr SerializeVars(const CustomVarObject::Ptr& object);
static const char* GetNotificationTypeByEnum(NotificationType type);
static Dictionary::Ptr SerializeVars(const Dictionary::Ptr& vars);
static String HashValue(const Value& value);
static String HashValue(const Value& value, const std::set<String>& propertiesBlacklist, bool propertiesWhitelist = false);
@ -135,6 +148,17 @@ private:
static void NextCheckChangedHandler(const Checkable::Ptr& checkable);
static void AcknowledgementSetHandler(const Checkable::Ptr& checkable, const String& author, const String& comment, AcknowledgementType type, bool persistent, double changeTime, double expiry);
static void AcknowledgementClearedHandler(const Checkable::Ptr& checkable, const String& removedBy, double changeTime);
static void NotificationUsersChangedHandler(const Notification::Ptr& notification, const Array::Ptr& oldValues, const Array::Ptr& newValues);
static void NotificationUserGroupsChangedHandler(const Notification::Ptr& notification, const Array::Ptr& oldValues, const Array::Ptr& newValues);
static void TimePeriodRangesChangedHandler(const TimePeriod::Ptr& timeperiod, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
static void TimePeriodIncludesChangedHandler(const TimePeriod::Ptr& timeperiod, const Array::Ptr& oldValues, const Array::Ptr& newValues);
static void TimePeriodExcludesChangedHandler(const TimePeriod::Ptr& timeperiod, const Array::Ptr& oldValues, const Array::Ptr& newValues);
static void UserGroupsChangedHandler(const User::Ptr& user, const Array::Ptr&, const Array::Ptr& newValues);
static void HostGroupsChangedHandler(const Host::Ptr& host, const Array::Ptr& oldValues, const Array::Ptr& newValues);
static void ServiceGroupsChangedHandler(const Service::Ptr& service, const Array::Ptr& oldValues, const Array::Ptr& newValues);
static void CommandEnvChangedHandler(const ConfigObject::Ptr& command, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
static void CommandArgumentsChangedHandler(const ConfigObject::Ptr& command, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
static void CustomVarsChangedHandler(const ConfigObject::Ptr& object, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
void AssertOnWorkQueue();

View File

@ -70,6 +70,7 @@ add_boost_test(base
base_dictionary/remove
base_dictionary/clone
base_dictionary/json
base_dictionary/keys_ordered
base_fifo/construct
base_fifo/io
base_json/encode

View File

@ -3,6 +3,8 @@
#include "base/dictionary.hpp"
#include "base/objectlock.hpp"
#include "base/json.hpp"
#include "base/string.hpp"
#include "base/utility.hpp"
#include <BoostTestTargetConfig.h>
using namespace icinga;
@ -183,4 +185,16 @@ BOOST_AUTO_TEST_CASE(json)
BOOST_CHECK(deserialized->Get("test2") == "hello world");
}
BOOST_AUTO_TEST_CASE(keys_ordered)
{
Dictionary::Ptr dictionary = new Dictionary();
for (int i = 0; i < 100; i++) {
dictionary->Set(std::to_string(Utility::Random()), Utility::Random());
}
std::vector<String> keys = dictionary->GetKeys();
BOOST_CHECK(std::is_sorted(keys.begin(), keys.end()));
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -134,6 +134,7 @@ no_user_view { yylval->num = FANoUserView; return T_FIELD_ATTRIBUTE; }
deprecated { yylval->num = FADeprecated; return T_FIELD_ATTRIBUTE; }
get_virtual { yylval->num = FAGetVirtual; return T_FIELD_ATTRIBUTE; }
set_virtual { yylval->num = FASetVirtual; return T_FIELD_ATTRIBUTE; }
signal_with_old_value { yylval->num = FASignalWithOldValue; return T_FIELD_ATTRIBUTE; }
virtual { yylval->num = FAGetVirtual | FASetVirtual; return T_FIELD_ATTRIBUTE; }
navigation { return T_NAVIGATION; }
validator { return T_VALIDATOR; }

View File

@ -830,10 +830,10 @@ void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&)
m_Impl << "void ObjectImpl<" << klass.Name << ">::Set" << field.GetFriendlyName() << "(" << field.Type.GetArgumentType() << " value, bool suppress_events, const Value& cookie)" << std::endl
<< "{" << std::endl;
if (field.Type.IsName || !field.TrackAccessor.empty())
m_Impl << "\t" << "Value oldValue = Get" << field.GetFriendlyName() << "();" << std::endl;
if (field.Type.IsName || !field.TrackAccessor.empty() || field.Attributes & FASignalWithOldValue)
m_Impl << "\t" << "Value oldValue = Get" << field.GetFriendlyName() << "();" << std::endl
<< "\t" << "auto *dobj = dynamic_cast<ConfigObject *>(this);" << std::endl;
if (field.SetAccessor.empty() && !(field.Attributes & FANoStorage))
m_Impl << "\t" << "m_" << field.GetFriendlyName() << ".store(value);" << std::endl;
else
@ -841,16 +841,22 @@ void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&)
if (field.Type.IsName || !field.TrackAccessor.empty()) {
if (field.Name != "active") {
m_Impl << "\t" << "auto *dobj = dynamic_cast<ConfigObject *>(this);" << std::endl
<< "\t" << "if (!dobj || dobj->IsActive())" << std::endl
m_Impl << "\t" << "if (!dobj || dobj->IsActive())" << std::endl
<< "\t";
}
m_Impl << "\t" << "Track" << field.GetFriendlyName() << "(oldValue, value);" << std::endl;
}
m_Impl << "\t" << "if (!suppress_events)" << std::endl
<< "\t\t" << "Notify" << field.GetFriendlyName() << "(cookie);" << std::endl
m_Impl << "\t" << "if (!suppress_events) {" << std::endl
<< "\t\t" << "Notify" << field.GetFriendlyName() << "(cookie);" << std::endl;
if (field.Attributes & FASignalWithOldValue) {
m_Impl << "\t\t" << "if (!dobj || dobj->IsActive())" << std::endl
<< "\t\t\t" << "On" << field.GetFriendlyName() << "ChangedWithOldValue(static_cast<" << klass.Name << " *>(this), oldValue, value);" << std::endl;
}
m_Impl << "\t" "}" << std::endl << std::endl
<< "}" << std::endl << std::endl;
}
}
@ -1053,6 +1059,15 @@ void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&)
for (const Field& field : klass.Fields) {
m_Header << "\t" << "static boost::signals2::signal<void (const intrusive_ptr<" << klass.Name << ">&, const Value&)> On" << field.GetFriendlyName() << "Changed;" << std::endl;
m_Impl << std::endl << "boost::signals2::signal<void (const intrusive_ptr<" << klass.Name << ">&, const Value&)> ObjectImpl<" << klass.Name << ">::On" << field.GetFriendlyName() << "Changed;" << std::endl << std::endl;
if (field.Attributes & FASignalWithOldValue) {
m_Header << "\t" << "static boost::signals2::signal<void (const intrusive_ptr<" << klass.Name
<< ">&, const Value&, const Value&)> On" << field.GetFriendlyName() << "ChangedWithOldValue;"
<< std::endl;
m_Impl << std::endl << "boost::signals2::signal<void (const intrusive_ptr<" << klass.Name
<< ">&, const Value&, const Value&)> ObjectImpl<" << klass.Name << ">::On"
<< field.GetFriendlyName() << "ChangedWithOldValue;" << std::endl << std::endl;
}
}
}

View File

@ -60,7 +60,8 @@ enum FieldAttribute
FADeprecated = 4096,
FAGetVirtual = 8192,
FASetVirtual = 16384,
FAActivationPriority = 32768
FAActivationPriority = 32768,
FASignalWithOldValue = 65536,
};
struct FieldType