1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
|
/* Icinga 2 | (c) 2024 Icinga GmbH | GPLv2+ */
#include "icinga/dependency.hpp"
#include "base/object-packer.hpp"
using namespace icinga;
boost::signals2::signal<void(const Checkable::Ptr&, const DependencyGroup::Ptr&)> DependencyGroup::OnChildRegistered;
boost::signals2::signal<void(const DependencyGroup::Ptr&, const std::vector<Dependency::Ptr>&, bool)> DependencyGroup::OnChildRemoved;
std::mutex DependencyGroup::m_RegistryMutex;
DependencyGroup::RegistryType DependencyGroup::m_Registry;
/**
* Register the provided dependency group to the global dependency group registry.
*
* In case there is already an identical dependency group in the registry, the provided dependency group is merged
* with the existing one, and that group is returned. Otherwise, the provided dependency group is registered as is,
* and it's returned.
*
* @param dependencyGroup The dependency group to register.
*/
DependencyGroup::Ptr DependencyGroup::Register(const DependencyGroup::Ptr& dependencyGroup)
{
std::lock_guard lock(m_RegistryMutex);
if (auto [it, inserted] = m_Registry.insert(dependencyGroup); !inserted) {
dependencyGroup->CopyDependenciesTo(*it);
return *it;
}
return dependencyGroup;
}
/**
* Detach the provided child Checkable from the specified dependency group.
*
* Unregisters all the dependency objects the child Checkable depends on from the provided dependency group and
* removes the dependency group from the global registry if it becomes empty afterward.
*
* @param dependencyGroup The dependency group to unregister the child Checkable from.
* @param child The child Checkable to detach from the dependency group.
*
* @return - Returns the dependency objects of the child Checkable that were member of the provided dependency group
* and a boolean indicating whether the dependency group has been erased from the global registry.
*/
std::pair<std::set<Dependency::Ptr>, bool> DependencyGroup::Unregister(const DependencyGroup::Ptr& dependencyGroup, const Checkable::Ptr& child)
{
std::lock_guard lock(m_RegistryMutex);
if (auto it(m_Registry.find(dependencyGroup)); it != m_Registry.end()) {
auto& existingGroup(*it);
auto dependencies(existingGroup->GetDependenciesForChild(child.get()));
for (const auto& dependency : dependencies) {
existingGroup->RemoveDependency(dependency);
}
bool remove = !existingGroup->HasChildren();
if (remove) {
m_Registry.erase(it);
}
return {{dependencies.begin(), dependencies.end()}, remove};
}
return {{}, false};
}
/**
* Retrieve the size of the global dependency group registry.
*
* @return size_t - Returns the size of the global dependency groups registry.
*/
size_t DependencyGroup::GetRegistrySize()
{
std::lock_guard lock(m_RegistryMutex);
return m_Registry.size();
}
DependencyGroup::DependencyGroup(String name, const std::set<Dependency::Ptr>& dependencies)
: m_RedundancyGroupName(std::move(name))
{
for (const auto& dependency : dependencies) {
m_Members[MakeCompositeKeyFor(dependency)].emplace(dependency->GetChild().get(), dependency.get());
}
}
/**
* Create a composite key for the provided dependency.
*
* The composite key consists of all the properties of the provided dependency object that influence its availability.
*
* @param dependency The dependency object to create a composite key for.
*
* @return - Returns the composite key for the provided dependency.
*/
DependencyGroup::CompositeKeyType DependencyGroup::MakeCompositeKeyFor(const Dependency::Ptr& dependency)
{
return std::make_tuple(
dependency->GetParent().get(),
dependency->GetPeriod().get(),
dependency->GetStateFilter(),
dependency->GetIgnoreSoftStates()
);
}
/**
* Check if the current dependency has any children.
*
* @return bool - Returns true if the current dependency group has children, otherwise false.
*/
bool DependencyGroup::HasChildren() const
{
std::lock_guard lock(m_Mutex);
return std::any_of(m_Members.begin(), m_Members.end(), [](const auto& pair) { return !pair.second.empty(); });
}
/**
* Retrieve all dependency objects of the current dependency group the provided child Checkable depend on.
*
* @param child The child Checkable to get the dependencies for.
*
* @return - Returns all the dependencies of the provided child Checkable in the current dependency group.
*/
std::vector<Dependency::Ptr> DependencyGroup::GetDependenciesForChild(const Checkable* child) const
{
std::lock_guard lock(m_Mutex);
std::vector<Dependency::Ptr> dependencies;
for (auto& [_, children] : m_Members) {
auto [begin, end] = children.equal_range(child);
std::transform(begin, end, std::back_inserter(dependencies), [](const auto& pair) {
return pair.second;
});
}
return dependencies;
}
/**
* Load all parent Checkables of the current dependency group.
*
* @param parents The set to load the parent Checkables into.
*/
void DependencyGroup::LoadParents(std::set<Checkable::Ptr>& parents) const
{
for (auto& [compositeKey, children] : m_Members) {
parents.insert(std::get<0>(compositeKey));
}
}
/**
* Retrieve the number of dependency objects in the current dependency group.
*
* This function mainly exists for optimization purposes, i.e. instead of getting a copy of the members and
* counting them, we can directly query the number of dependencies in the group.
*
* @return size_t
*/
size_t DependencyGroup::GetDependenciesCount() const
{
std::lock_guard lock(m_Mutex);
size_t count(0);
for (auto& [_, dependencies] : m_Members) {
count += dependencies.size();
}
return count;
}
/**
* Add a dependency object to the current dependency group.
*
* @param dependency The dependency to add to the dependency group.
*/
void DependencyGroup::AddDependency(const Dependency::Ptr& dependency)
{
std::lock_guard lock(m_Mutex);
auto compositeKey(MakeCompositeKeyFor(dependency));
auto it = m_Members.find(compositeKey);
// The dependency must be compatible with the group, i.e. its parent config must be known in the group already.
VERIFY(it != m_Members.end());
it->second.emplace(dependency->GetChild().get(), dependency.get());
}
/**
* Remove a dependency object from the current dependency group.
*
* @param dependency The dependency to remove from the dependency group.
*/
void DependencyGroup::RemoveDependency(const Dependency::Ptr& dependency)
{
std::lock_guard lock(m_Mutex);
if (auto it(m_Members.find(MakeCompositeKeyFor(dependency))); it != m_Members.end()) {
auto [begin, end] = it->second.equal_range(dependency->GetChild().get());
for (auto childrenIt(begin); childrenIt != end; ++childrenIt) {
if (childrenIt->second == dependency) {
// This will also remove the child Checkable from the multimap container
// entirely if this was the last child of it.
it->second.erase(childrenIt);
return;
}
}
}
}
/**
* Copy the dependency objects of the current dependency group to the provided dependency group (destination).
*
* @param dest The dependency group to copy the dependencies to.
*/
void DependencyGroup::CopyDependenciesTo(const DependencyGroup::Ptr& dest)
{
VERIFY(this != dest); // Prevent from doing something stupid, i.e. deadlocking ourselves.
std::lock_guard lock(m_Mutex);
for (auto& [_, children] : m_Members) {
std::for_each(children.begin(), children.end(), [&dest](const auto& pair) {
dest->AddDependency(pair.second);
});
}
}
/**
* Set the Icinga DB identifier for the current dependency group.
*
* The only usage of this function is the Icinga DB feature used to cache the unique hash of this dependency groups.
*
* @param identifier The Icinga DB identifier to set.
*/
void DependencyGroup::SetIcingaDBIdentifier(const String& identifier)
{
std::lock_guard lock(m_Mutex);
m_IcingaDBIdentifier = identifier;
}
/**
* Retrieve the Icinga DB identifier for the current dependency group.
*
* When the identifier is not already set by Icinga DB via the SetIcingaDBIdentifier method,
* this will just return an empty string.
*
* @return - Returns the Icinga DB identifier for the current dependency group.
*/
String DependencyGroup::GetIcingaDBIdentifier() const
{
std::lock_guard lock(m_Mutex);
return m_IcingaDBIdentifier;
}
/**
* Retrieve the redundancy group name of the current dependency group.
*
* If the current dependency group doesn't represent a redundancy group, this will return an empty string.
*
* @return - Returns the name of the current dependency group.
*/
const String& DependencyGroup::GetRedundancyGroupName() const
{
// We don't need to lock the mutex here, as the name is set once during
// the object construction and never changed afterwards.
return m_RedundancyGroupName;
}
/**
* Retrieve the unique composite key of the current dependency group.
*
* The composite key consists of some unique data of the group members, and should be used to generate
* a unique deterministic hash for the dependency group. Additionally, for explicitly configured redundancy
* groups, the non-unique dependency group name is also included on top of the composite keys.
*
* @return - Returns the composite key of the current dependency group.
*/
String DependencyGroup::GetCompositeKey()
{
// This a copy of the CompositeKeyType definition but with the String type instead of Checkable* and TimePeriod*.
// This is because we need to produce a deterministic value from the composite key after each restart and that's
// not achievable using pointers.
using StringTuple = std::tuple<String, String, int, bool>;
std::vector<StringTuple> compositeKeys;
for (auto& [compositeKey, _] : m_Members) {
auto [parent, tp, stateFilter, ignoreSoftStates] = compositeKey;
compositeKeys.emplace_back(parent->GetName(), tp ? tp->GetName() : "", stateFilter, ignoreSoftStates);
}
// IMPORTANT: The order of the composite keys must be sorted to ensure the deterministic hash value.
std::sort(compositeKeys.begin(), compositeKeys.end());
Array::Ptr data(new Array{GetRedundancyGroupName()});
for (auto& compositeKey : compositeKeys) {
// std::apply is used to unpack the composite key tuple and add its elements to the data array.
// It's like manually expanding the tuple into x variables and then adding them one by one to the array.
// See https://en.cppreference.com/w/cpp/language/fold for more information.
std::apply([&data](auto&&... args) { (data->Add(std::move(args)), ...); }, std::move(compositeKey));
}
return PackObject(data);
}
/**
* Retrieve the state of the current dependency group.
*
* The state of the dependency group is determined based on the state of the parent Checkables and dependency objects
* of the group. A dependency group is considered unreachable when none of the parent Checkables is reachable. However,
* a dependency group may still be marked as failed even when it has reachable parent Checkables, but an unreachable
* group has always a failed state.
*
* @param child The child Checkable to evaluate the state for.
* @param dt The dependency type to evaluate the state for, defaults to DependencyState.
* @param rstack The recursion stack level to prevent infinite recursion, defaults to 0.
*
* @return - Returns the state of the current dependency group.
*/
DependencyGroup::State DependencyGroup::GetState(const Checkable* child, DependencyType dt, int rstack) const
{
auto dependencies(GetDependenciesForChild(child));
size_t reachable = 0, available = 0;
for (const auto& dependency : dependencies) {
if (dependency->GetParent()->IsReachable(dt, rstack)) {
reachable++;
// Only reachable parents are considered for availability. If they are unreachable and checks are
// disabled, they could be incorrectly treated as available otherwise.
if (dependency->IsAvailable(dt)) {
available++;
}
}
}
if (IsRedundancyGroup()) {
// The state of a redundancy group is determined by the best state of any parent. If any parent ist reachable,
// the redundancy group is reachable, analogously for availability.
if (reachable == 0) {
return State::Unreachable;
} else if (available == 0) {
return State::Failed;
} else {
return State::Ok;
}
} else {
// For dependencies without a redundancy group, dependencies.size() will be 1 in almost all cases. It will only
// contain more elements if there are duplicate dependency config objects between two checkables. In this case,
// all of them have to be reachable/available as they don't provide redundancy.
if (reachable < dependencies.size()) {
return State::Unreachable;
} else if (available < dependencies.size()) {
return State::Failed;
} else {
return State::Ok;
}
}
}
|