diff --git a/doc/19-technical-concepts.md b/doc/19-technical-concepts.md
index 0394bce7f..3deef42d0 100644
--- a/doc/19-technical-concepts.md
+++ b/doc/19-technical-concepts.md
@@ -1874,6 +1874,39 @@ Message updates will be dropped when:
* Checkable does not exist.
* Origin endpoint's zone is not allowed to access this checkable.
+#### event::SetRemovalInfo
+
+> Location: `clusterevents.cpp`
+
+##### Message Body
+
+Key | Value
+----------|---------
+jsonrpc | 2.0
+method | event::SetRemovalInfo
+params | Dictionary
+
+##### Params
+
+Key | Type | Description
+---------------|-------------|---------------------------------
+object\_type | String | Object type (`"Comment"` or `"Downtime"`)
+object\_name | String | Object name
+removed\_by | String | Name of the removal requestor
+remove\_time | Timestamp | Time of the remove operation
+
+##### Functions
+
+**Event Sender**: `Comment::OnRemovalInfoChanged` and `Downtime::OnRemovalInfoChanged`
+**Event Receiver**: `SetRemovalInfoAPIHandler`
+
+This message is used to synchronize information about manual comment and downtime removals before deleting the
+corresponding object.
+
+##### Permissions
+
+This message is only accepted from the local zone and from parent zones.
+
#### config::Update
> Location: `apilistener-filesync.cpp`
diff --git a/lib/icinga/apiactions.cpp b/lib/icinga/apiactions.cpp
index 89ca3b9f3..f6999dbf0 100644
--- a/lib/icinga/apiactions.cpp
+++ b/lib/icinga/apiactions.cpp
@@ -311,12 +311,7 @@ Dictionary::Ptr ApiActions::RemoveComment(const ConfigObject::Ptr& object,
std::set comments = checkable->GetComments();
for (const Comment::Ptr& comment : comments) {
- {
- ObjectLock oLock (comment);
- comment->SetRemovedBy(author);
- }
-
- Comment::RemoveComment(comment->GetName());
+ Comment::RemoveComment(comment->GetName(), true, author);
}
return ApiActions::CreateResult(200, "Successfully removed all comments for object '" + checkable->GetName() + "'.");
@@ -327,14 +322,9 @@ Dictionary::Ptr ApiActions::RemoveComment(const ConfigObject::Ptr& object,
if (!comment)
return ApiActions::CreateResult(404, "Cannot remove non-existent comment object.");
- {
- ObjectLock oLock (comment);
- comment->SetRemovedBy(author);
- }
-
String commentName = comment->GetName();
- Comment::RemoveComment(commentName);
+ Comment::RemoveComment(commentName, true, author);
return ApiActions::CreateResult(200, "Successfully removed comment '" + commentName + "'.");
}
@@ -507,15 +497,10 @@ Dictionary::Ptr ApiActions::RemoveDowntime(const ConfigObject::Ptr& object,
std::set downtimes = checkable->GetDowntimes();
for (const Downtime::Ptr& downtime : downtimes) {
- {
- ObjectLock oLock (downtime);
- downtime->SetRemovedBy(author);
- }
-
childCount += downtime->GetChildren().size();
try {
- Downtime::RemoveDowntime(downtime->GetName(), true, true);
+ Downtime::RemoveDowntime(downtime->GetName(), true, true, false, author);
} catch (const invalid_downtime_removal_error& error) {
Log(LogWarning, "ApiActions") << error.what();
@@ -532,16 +517,11 @@ Dictionary::Ptr ApiActions::RemoveDowntime(const ConfigObject::Ptr& object,
if (!downtime)
return ApiActions::CreateResult(404, "Cannot remove non-existent downtime object.");
- {
- ObjectLock oLock (downtime);
- downtime->SetRemovedBy(author);
- }
-
childCount += downtime->GetChildren().size();
try {
String downtimeName = downtime->GetName();
- Downtime::RemoveDowntime(downtimeName, true, true);
+ Downtime::RemoveDowntime(downtimeName, true, true, false, author);
return ApiActions::CreateResult(200, "Successfully removed downtime '" + downtimeName +
"' and " + std::to_string(childCount) + " child downtimes.");
diff --git a/lib/icinga/clusterevents.cpp b/lib/icinga/clusterevents.cpp
index 0bb4a06a0..bade7e5a5 100644
--- a/lib/icinga/clusterevents.cpp
+++ b/lib/icinga/clusterevents.cpp
@@ -38,6 +38,7 @@ REGISTER_APIFUNCTION(NotificationSentUser, event, &ClusterEvents::NotificationSe
REGISTER_APIFUNCTION(NotificationSentToAllUsers, event, &ClusterEvents::NotificationSentToAllUsersAPIHandler);
REGISTER_APIFUNCTION(ExecutedCommand, event, &ClusterEvents::ExecutedCommandAPIHandler);
REGISTER_APIFUNCTION(UpdateExecutions, event, &ClusterEvents::UpdateExecutionsAPIHandler);
+REGISTER_APIFUNCTION(SetRemovalInfo, event, &ClusterEvents::SetRemovalInfoAPIHandler);
void ClusterEvents::StaticInitialize()
{
@@ -55,6 +56,9 @@ void ClusterEvents::StaticInitialize()
Checkable::OnAcknowledgementSet.connect(&ClusterEvents::AcknowledgementSetHandler);
Checkable::OnAcknowledgementCleared.connect(&ClusterEvents::AcknowledgementClearedHandler);
+
+ Comment::OnRemovalInfoChanged.connect(&ClusterEvents::SetRemovalInfoHandler);
+ Downtime::OnRemovalInfoChanged.connect(&ClusterEvents::SetRemovalInfoHandler);
}
Dictionary::Ptr ClusterEvents::MakeCheckResultMessage(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
@@ -1307,3 +1311,62 @@ Value ClusterEvents::UpdateExecutionsAPIHandler(const MessageOrigin::Ptr& origin
return Empty;
}
+
+void ClusterEvents::SetRemovalInfoHandler(const ConfigObject::Ptr& obj, const String& removedBy, double removeTime,
+ const MessageOrigin::Ptr& origin)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("object_type", obj->GetReflectionType()->GetName());
+ params->Set("object_name", obj->GetName());
+ params->Set("removed_by", removedBy);
+ params->Set("remove_time", removeTime);
+
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::SetRemovalInfo");
+ message->Set("params", params);
+
+ listener->RelayMessage(origin, obj, message, true);
+}
+
+Value ClusterEvents::SetRemovalInfoAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint || (origin->FromZone && !Zone::GetLocalZone()->IsChildOf(origin->FromZone))) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'set removal info' message from '" << origin->FromClient->GetIdentity()
+ << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ String objectType = params->Get("object_type");
+ String objectName = params->Get("object_name");
+ String removedBy = params->Get("removed_by");
+ double removeTime = params->Get("remove_time");
+
+ if (objectType == Comment::GetTypeName()) {
+ Comment::Ptr comment = Comment::GetByName(objectName);
+
+ if (comment) {
+ comment->SetRemovalInfo(removedBy, removeTime, origin);
+ }
+ } else if (objectType == Downtime::GetTypeName()) {
+ Downtime::Ptr downtime = Downtime::GetByName(objectName);
+
+ if (downtime) {
+ downtime->SetRemovalInfo(removedBy, removeTime, origin);
+ }
+ } else {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'set removal info' message from '" << origin->FromClient->GetIdentity()
+ << "': Unknown object type.";
+ }
+
+ return Empty;
+}
diff --git a/lib/icinga/clusterevents.hpp b/lib/icinga/clusterevents.hpp
index 2a372d347..a6c21971a 100644
--- a/lib/icinga/clusterevents.hpp
+++ b/lib/icinga/clusterevents.hpp
@@ -69,6 +69,9 @@ public:
static Value ExecutedCommandAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
static Value UpdateExecutionsAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+ static void SetRemovalInfoHandler(const ConfigObject::Ptr& obj, const String& removedBy, double removeTime, const MessageOrigin::Ptr& origin);
+ static Value SetRemovalInfoAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
static int GetCheckRequestQueueSize();
static void LogRemoteCheckQueueInformation();
diff --git a/lib/icinga/comment.cpp b/lib/icinga/comment.cpp
index eeb9373f4..7af67df25 100644
--- a/lib/icinga/comment.cpp
+++ b/lib/icinga/comment.cpp
@@ -18,6 +18,7 @@ static Timer::Ptr l_CommentsExpireTimer;
boost::signals2::signal Comment::OnCommentAdded;
boost::signals2::signal Comment::OnCommentRemoved;
+boost::signals2::signal Comment::OnRemovalInfoChanged;
REGISTER_TYPE(Comment);
@@ -185,7 +186,8 @@ String Comment::AddComment(const Checkable::Ptr& checkable, CommentType entryTyp
return fullName;
}
-void Comment::RemoveComment(const String& id, const MessageOrigin::Ptr& origin)
+void Comment::RemoveComment(const String& id, bool removedManually, const String& removedBy,
+ const MessageOrigin::Ptr& origin)
{
Comment::Ptr comment = Comment::GetByName(id);
@@ -195,6 +197,10 @@ void Comment::RemoveComment(const String& id, const MessageOrigin::Ptr& origin)
Log(LogNotice, "Comment")
<< "Removed comment '" << comment->GetName() << "' from object '" << comment->GetCheckable()->GetName() << "'.";
+ if (removedManually) {
+ comment->SetRemovalInfo(removedBy, Utility::GetTime());
+ }
+
Array::Ptr errors = new Array();
if (!ConfigObjectUtility::DeleteObject(comment, false, errors, nullptr)) {
@@ -207,6 +213,17 @@ void Comment::RemoveComment(const String& id, const MessageOrigin::Ptr& origin)
}
}
+void Comment::SetRemovalInfo(const String& removedBy, double removeTime, const MessageOrigin::Ptr& origin) {
+ {
+ ObjectLock olock(this);
+
+ SetRemovedBy(removedBy, false, origin);
+ SetRemoveTime(removeTime, false, origin);
+ }
+
+ OnRemovalInfoChanged(this, removedBy, removeTime, origin);
+}
+
String Comment::GetCommentIDFromLegacyID(int id)
{
std::unique_lock lock(l_CommentMutex);
diff --git a/lib/icinga/comment.hpp b/lib/icinga/comment.hpp
index 166654d31..54410f63d 100644
--- a/lib/icinga/comment.hpp
+++ b/lib/icinga/comment.hpp
@@ -24,18 +24,22 @@ public:
static boost::signals2::signal OnCommentAdded;
static boost::signals2::signal OnCommentRemoved;
+ static boost::signals2::signal OnRemovalInfoChanged;
intrusive_ptr GetCheckable() const;
bool IsExpired() const;
+ void SetRemovalInfo(const String& removedBy, double removeTime, const MessageOrigin::Ptr& origin = nullptr);
+
static int GetNextCommentID();
static String AddComment(const intrusive_ptr& checkable, CommentType entryType,
const String& author, const String& text, bool persistent, double expireTime,
const String& id = String(), const MessageOrigin::Ptr& origin = nullptr);
- static void RemoveComment(const String& id, const MessageOrigin::Ptr& origin = nullptr);
+ static void RemoveComment(const String& id, bool removedManually = false, const String& removedBy = "",
+ const MessageOrigin::Ptr& origin = nullptr);
static String GetCommentIDFromLegacyID(int id);
diff --git a/lib/icinga/comment.ti b/lib/icinga/comment.ti
index 678bcabd8..3c45bf17c 100644
--- a/lib/icinga/comment.ti
+++ b/lib/icinga/comment.ti
@@ -73,6 +73,7 @@ class Comment : ConfigObject < CommentNameComposer
[state] int legacy_id;
[no_user_view, no_user_modify] String removed_by;
+ [no_user_view, no_user_modify] Timestamp remove_time;
};
}
diff --git a/lib/icinga/downtime.cpp b/lib/icinga/downtime.cpp
index bdfac1e20..f19698e64 100644
--- a/lib/icinga/downtime.cpp
+++ b/lib/icinga/downtime.cpp
@@ -23,6 +23,7 @@ boost::signals2::signal Downtime::OnDowntimeAdded;
boost::signals2::signal Downtime::OnDowntimeRemoved;
boost::signals2::signal Downtime::OnDowntimeStarted;
boost::signals2::signal Downtime::OnDowntimeTriggered;
+boost::signals2::signal Downtime::OnRemovalInfoChanged;
REGISTER_TYPE(Downtime);
@@ -331,7 +332,8 @@ Downtime::Ptr Downtime::AddDowntime(const Checkable::Ptr& checkable, const Strin
return downtime;
}
-void Downtime::RemoveDowntime(const String& id, bool includeChildren, bool cancelled, bool expired, const MessageOrigin::Ptr& origin)
+void Downtime::RemoveDowntime(const String& id, bool includeChildren, bool cancelled, bool expired,
+ const String& removedBy, const MessageOrigin::Ptr& origin)
{
Downtime::Ptr downtime = Downtime::GetByName(id);
@@ -351,7 +353,9 @@ void Downtime::RemoveDowntime(const String& id, bool includeChildren, bool cance
}
}
- downtime->SetWasCancelled(cancelled);
+ if (cancelled) {
+ downtime->SetRemovalInfo(removedBy, Utility::GetTime());
+ }
Array::Ptr errors = new Array();
@@ -454,6 +458,17 @@ void Downtime::TriggerDowntime(double triggerTime)
OnDowntimeTriggered(this);
}
+void Downtime::SetRemovalInfo(const String& removedBy, double removeTime, const MessageOrigin::Ptr& origin) {
+ {
+ ObjectLock olock(this);
+
+ SetRemovedBy(removedBy, false, origin);
+ SetRemoveTime(removeTime, false, origin);
+ }
+
+ OnRemovalInfoChanged(this, removedBy, removeTime, origin);
+}
+
String Downtime::GetDowntimeIDFromLegacyID(int id)
{
std::unique_lock lock(l_DowntimeMutex);
diff --git a/lib/icinga/downtime.hpp b/lib/icinga/downtime.hpp
index 9c0368cb4..38d4e41e4 100644
--- a/lib/icinga/downtime.hpp
+++ b/lib/icinga/downtime.hpp
@@ -33,6 +33,7 @@ public:
static boost::signals2::signal OnDowntimeRemoved;
static boost::signals2::signal OnDowntimeStarted;
static boost::signals2::signal OnDowntimeTriggered;
+ static boost::signals2::signal OnRemovalInfoChanged;
intrusive_ptr GetCheckable() const;
@@ -51,13 +52,15 @@ public:
const String& scheduledBy = String(), const String& parent = String(), const String& id = String(),
const MessageOrigin::Ptr& origin = nullptr);
- static void RemoveDowntime(const String& id, bool includeChildren, bool cancelled, bool expired = false, const MessageOrigin::Ptr& origin = nullptr);
+ static void RemoveDowntime(const String& id, bool includeChildren, bool cancelled, bool expired = false,
+ const String& removedBy = "", const MessageOrigin::Ptr& origin = nullptr);
void RegisterChild(const Downtime::Ptr& downtime);
void UnregisterChild(const Downtime::Ptr& downtime);
std::set GetChildren() const;
void TriggerDowntime(double triggerTime);
+ void SetRemovalInfo(const String& removedBy, double removeTime, const MessageOrigin::Ptr& origin = nullptr);
static String GetDowntimeIDFromLegacyID(int id);
diff --git a/lib/icinga/downtime.ti b/lib/icinga/downtime.ti
index 502f48fc2..d21f0ebf5 100644
--- a/lib/icinga/downtime.ti
+++ b/lib/icinga/downtime.ti
@@ -68,7 +68,10 @@ class Downtime : ConfigObject < DowntimeNameComposer
default {{{ return new Array(); }}}
};
[state] int legacy_id;
- [state] bool was_cancelled;
+ [state] Timestamp remove_time;
+ [no_storage] bool was_cancelled {
+ get {{{ return GetRemoveTime() > 0; }}}
+ };
[config] String config_owner;
[config] String config_owner_hash;
[config] String authoritative_zone;
diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp
index 539e20804..e075db183 100644
--- a/lib/icingadb/icingadb-objects.cpp
+++ b/lib/icingadb/icingadb-objects.cpp
@@ -1902,7 +1902,7 @@ void IcingaDB::SendRemovedDowntime(const Downtime::Ptr& downtime)
"scheduled_end_time", Convert::ToString(TimestampToMilliseconds(downtime->GetEndTime())),
"has_been_cancelled", Convert::ToString((unsigned short)downtime->GetWasCancelled()),
"trigger_time", Convert::ToString(TimestampToMilliseconds(downtime->GetTriggerTime())),
- "cancel_time", Convert::ToString(TimestampToMilliseconds(Utility::GetTime())),
+ "cancel_time", Convert::ToString(TimestampToMilliseconds(downtime->GetRemoveTime())),
"event_id", CalcEventID("downtime_end", downtime),
"event_type", "downtime_end"
});
@@ -2057,9 +2057,10 @@ void IcingaDB::SendRemovedComment(const Comment::Ptr& comment)
xAdd.emplace_back(GetObjectIdentifier(endpoint));
}
- if (comment->GetExpireTime() < Utility::GetTime()) {
+ double removeTime = comment->GetRemoveTime();
+ if (removeTime > 0) {
xAdd.emplace_back("remove_time");
- xAdd.emplace_back(Convert::ToString(TimestampToMilliseconds(Utility::GetTime())));
+ xAdd.emplace_back(Convert::ToString(TimestampToMilliseconds(removeTime)));
xAdd.emplace_back("has_been_removed");
xAdd.emplace_back("1");
xAdd.emplace_back("removed_by");