Skip to content

Commit d2bf5d8

Browse files
authored
Merge pull request #10933 from Icinga/backport-10830-to-support/2.16
[Backport support/2.16] Track deleted runtime objects to avoid erroneous recreation
2 parents b891360 + 2a11941 commit d2bf5d8

6 files changed

Lines changed: 143 additions & 4 deletions

File tree

lib/base/dictionary.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,16 +155,17 @@ Dictionary::Iterator Dictionary::End()
155155
* Removes the item specified by the iterator from the dictionary.
156156
*
157157
* @param it The iterator.
158+
* @return an iterator to the element after the removed element.
158159
*/
159-
void Dictionary::Remove(Dictionary::Iterator it)
160+
Dictionary::Iterator Dictionary::Remove(Dictionary::Iterator it)
160161
{
161162
ASSERT(OwnsLock());
162163
std::unique_lock<std::shared_timed_mutex> lock (m_DataMutex);
163164

164165
if (m_Frozen)
165166
BOOST_THROW_EXCEPTION(std::invalid_argument("Dictionary must not be modified."));
166167

167-
m_Data.erase(it);
168+
return m_Data.erase(it);
168169
}
169170

170171
/**

lib/base/dictionary.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class Dictionary final : public Object
5656

5757
void Remove(const String& key);
5858

59-
void Remove(Iterator it);
59+
Iterator Remove(Iterator it);
6060

6161
void Clear();
6262

lib/remote/apilistener-configsync.cpp

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,16 @@ Value ApiListener::ConfigUpdateObjectAPIHandler(const MessageOrigin::Ptr& origin
9898
/* update the object */
9999
double objVersion = params->Get("version");
100100

101+
auto deletedVersion = listener->GetRuntimeObjectDeletionTs(objType, objName);
102+
if (deletedVersion >= objVersion) {
103+
Log(LogInformation, "ApiListener")
104+
<< "Ignoring config update for deleted object '" << objName << "' of type '" << objType << "' from '"
105+
<< identity << "' (endpoint: '" << endpoint->GetName() << "', zone: '" << endpointZone->GetName()
106+
<< "'): Object version " << std::fixed << objVersion << " is less recent than the deleted version "
107+
<< std::fixed << deletedVersion << ".";
108+
return Empty;
109+
}
110+
101111
Type::Ptr ptype = Type::GetByName(objType);
102112
auto *ctype = dynamic_cast<ConfigType *>(ptype.get());
103113

@@ -274,6 +284,24 @@ Value ApiListener::ConfigDeleteObjectAPIHandler(const MessageOrigin::Ptr& origin
274284

275285
ConfigObject::Ptr object = ctype->GetObject(objName);
276286

287+
// Check if the deletion is for an older object version
288+
double objVersion = params->Get("version");
289+
if (object && objVersion < object->GetVersion()) {
290+
Log(LogInformation, "ApiListener")
291+
<< "Ignoring 'config delete object' message"
292+
<< " from '" << identity << "' (endpoint: '" << endpoint->GetName() << "', zone: '" << endpointZone->GetName() << "')"
293+
<< " for object '" << object->GetName()
294+
<< "': Object version " << std::fixed << object->GetVersion()
295+
<< " is more recent than the deleted version " << std::fixed << objVersion << ".";
296+
297+
return Empty;
298+
}
299+
300+
/* We need to store the deletion timestamp even if no object exists, to protect against
301+
* endpoints that try to sync back objects that would have been deleted by this message.
302+
*/
303+
listener->UpdateRuntimeObjectDeletionTs(objType, objName, objVersion);
304+
277305
if (!object) {
278306
Log(LogNotice, "ApiListener")
279307
<< "Could not delete non-existent object '" << objName << "' with type '" << params->Get("type") << "'.";
@@ -437,6 +465,24 @@ void ApiListener::DeleteConfigObject(const ConfigObject::Ptr& object, const Mess
437465
if (object->GetPackage() != "_api")
438466
return;
439467

468+
auto typeName = object->GetReflectionType()->GetName();
469+
auto objName = object->GetName();
470+
471+
/* Update the deletion timestamp only if the deletion didn't originate from another
472+
* endpoint, because when this deletion was triggered via a JSON-RPC message, the
473+
* stored timestamp is the one that was set in `ApiListener::ConfigDeleteObjectAPIHandler`
474+
* and already reflects the time the original deletion was triggered.
475+
* Also set if no timestamp was stored yet, as a safeguard. There shouldn't be any
476+
* situation where this happens, but that relies on the correct execution order of
477+
* many other functions, like cascading deletes always propagating in the correct
478+
* order through JSON-RPC, so the children are always deleted first.
479+
*/
480+
double deletionTime = GetRuntimeObjectDeletionTs(typeName, objName);
481+
if (!origin || origin->IsLocal() || deletionTime == 0) {
482+
deletionTime = Utility::GetTime();
483+
UpdateRuntimeObjectDeletionTs(typeName, objName, deletionTime);
484+
}
485+
440486
/* only send objects to zones which have access to the object */
441487
if (client) {
442488
Zone::Ptr target_zone = client->GetEndpoint()->GetZone();
@@ -460,7 +506,7 @@ void ApiListener::DeleteConfigObject(const ConfigObject::Ptr& object, const Mess
460506

461507
params->Set("name", object->GetName());
462508
params->Set("type", object->GetReflectionType()->GetName());
463-
params->Set("version", object->GetVersion());
509+
params->Set("version", deletionTime);
464510

465511

466512
#ifdef I2_DEBUG
@@ -508,3 +554,79 @@ void ApiListener::SendRuntimeConfigObjects(const JsonRpcConnection::Ptr& aclient
508554
Log(LogInformation, "ApiListener")
509555
<< "Finished syncing runtime objects to endpoint '" << endpoint->GetName() << "'.";
510556
}
557+
558+
/**
559+
* Prunes the list of deleted runtime objects.
560+
*
561+
* This takes into account the longest log duration of all the endpoints in the cluster and
562+
* then deletes the entries for objects that are older than that.
563+
*
564+
* The reasoning is that once a deletion is older than log duration of an endpoint it is
565+
* unlikely that we could still get any delayed updates to the object versions that were deleted.
566+
* At that point, either all endpoints are up-to-date, or the problematic messages have been
567+
* dropped from the replay log anyway.
568+
*/
569+
void ApiListener::PruneDeletedRuntimeObjects()
570+
{
571+
auto deletedRuntimeObjects = GetDeletedRuntimeObjects();
572+
573+
double maxLogDuration = 0;
574+
for (const auto &endpoint : ConfigType::GetObjectsByType<Endpoint>()) {
575+
maxLogDuration = std::max(maxLogDuration, endpoint->GetLogDuration());
576+
}
577+
double cutoff = Utility::GetTime() - maxLogDuration;
578+
579+
ObjectLock lock(deletedRuntimeObjects);
580+
581+
for (auto it = deletedRuntimeObjects->Begin(); it != deletedRuntimeObjects->End();) {
582+
if (it->second < cutoff) {
583+
it = deletedRuntimeObjects->Remove(it);
584+
} else {
585+
it++;
586+
};
587+
}
588+
}
589+
590+
static String MakeDeletionTimestampKey(const String& typeName, const String& objName)
591+
{
592+
return typeName + ":" + objName;
593+
}
594+
595+
/**
596+
* Get the timestamp at which the object has been most recently deleted.
597+
*
598+
* @param typeName The name of the tyope of the object
599+
* @param objName The name of the object
600+
* @return the timestamp of the object, or 0 if there was no entry.
601+
*/
602+
double ApiListener::GetRuntimeObjectDeletionTs(const String& typeName, const String& objName)
603+
{
604+
auto dict = GetDeletedRuntimeObjects();
605+
auto ts = dict->Get(MakeDeletionTimestampKey(typeName, objName));
606+
if (ts.IsNumber()) {
607+
return ts.Get<double>();
608+
}
609+
return 0;
610+
}
611+
612+
/**
613+
* Updates the deletion timestamp for the object.
614+
*
615+
* The timestamp will not be changed in case a newer timestamp has already been stored
616+
* for the object.
617+
*
618+
* @param typeName The name of the tyope of the object
619+
* @param objName The name of the object
620+
* @param ts the timestamp
621+
*/
622+
bool ApiListener::UpdateRuntimeObjectDeletionTs(const String& typeName, const String& objName, double ts)
623+
{
624+
auto dict = GetDeletedRuntimeObjects();
625+
ObjectLock lock(dict);
626+
auto current = GetRuntimeObjectDeletionTs(typeName, objName);
627+
if (current > ts) {
628+
return false;
629+
}
630+
dict->Set(MakeDeletionTimestampKey(typeName, objName), ts);
631+
return true;
632+
}

lib/remote/apilistener.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,12 @@ void ApiListener::Start(bool runtimeCreated)
297297
m_Timer->Start();
298298
m_Timer->Reschedule(0);
299299

300+
m_DeletedRuntimeObjectsTimer = Timer::Create();
301+
m_DeletedRuntimeObjectsTimer->OnTimerExpired.connect([this](const Timer* const&) { PruneDeletedRuntimeObjects(); });
302+
m_DeletedRuntimeObjectsTimer->SetInterval(15 * 60);
303+
m_DeletedRuntimeObjectsTimer->Start();
304+
m_DeletedRuntimeObjectsTimer->Reschedule(0);
305+
300306
m_ReconnectTimer = Timer::Create();
301307
m_ReconnectTimer->OnTimerExpired.connect([this](const Timer * const&) { ApiReconnectTimerHandler(); });
302308
m_ReconnectTimer->SetInterval(10);
@@ -379,6 +385,7 @@ void ApiListener::Stop(bool runtimeDeleted)
379385
m_CleanupCertificateRequestsTimer->Stop(true);
380386
m_AuthorityTimer->Stop(true);
381387
m_ReconnectTimer->Stop(true);
388+
m_DeletedRuntimeObjectsTimer->Stop(true);
382389
m_Timer->Stop(true);
383390
m_RenewOwnCertTimer->Stop(true);
384391

lib/remote/apilistener.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ class ApiListener final : public ObjectImpl<ApiListener>
183183
std::set<HttpServerConnection::Ptr> m_HttpClients;
184184

185185
Timer::Ptr m_Timer;
186+
Timer::Ptr m_DeletedRuntimeObjectsTimer;
186187
Timer::Ptr m_ReconnectTimer;
187188
Timer::Ptr m_AuthorityTimer;
188189
Timer::Ptr m_CleanupCertificateRequestsTimer;
@@ -204,6 +205,10 @@ class ApiListener final : public ObjectImpl<ApiListener>
204205
void CleanupCertificateRequestsTimerHandler();
205206
void CheckApiPackageIntegrity();
206207

208+
void PruneDeletedRuntimeObjects();
209+
double GetRuntimeObjectDeletionTs(const String& typeName, const String& objName);
210+
bool UpdateRuntimeObjectDeletionTs(const String& typeName, const String& objName, double ts);
211+
207212
bool AddListener(const String& node, const String& service);
208213
void StopListener();
209214
void AddConnection(const Endpoint::Ptr& endpoint);

lib/remote/apilistener.ti

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ class ApiListener : ConfigObject
6767
[config, no_user_modify] bool enforce_filter_expression_permission {
6868
default {{{ return false; }}}
6969
};
70+
71+
[state, no_user_modify] Dictionary::Ptr deleted_runtime_objects {
72+
default {{{ return new Dictionary(); }}}
73+
};
7074
};
7175

7276
}

0 commit comments

Comments
 (0)