File: dependency-group.cpp

package info (click to toggle)
icinga2 2.15.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 20,040 kB
  • sloc: cpp: 97,870; sql: 3,261; cs: 1,636; yacc: 1,584; sh: 1,009; ansic: 890; lex: 420; python: 80; makefile: 62; javascript: 12
file content (348 lines) | stat: -rw-r--r-- 12,671 bytes parent folder | download
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;
		}
	}
}