Skip to content

Commit 8740638

Browse files
Track deleted runtime objects to avoid erroneous recreation
1 parent 7d953f9 commit 8740638

3 files changed

Lines changed: 88 additions & 1 deletion

File tree

lib/remote/apilistener-configsync.cpp

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

101+
if (listener->GetRuntimeObjectDeletionTs(objType, objName) >= objVersion) {
102+
Log(LogInformation, "ApiListener")
103+
<< "Ignoring config update for deleted object '" << objName << "' of type '" << objType << "' from '"
104+
<< identity << "' (endpoint: '" << endpoint->GetName() << "', zone: '" << endpointZone->GetName() << "')";
105+
return Empty;
106+
}
107+
101108
Type::Ptr ptype = Type::GetByName(objType);
102109
auto *ctype = dynamic_cast<ConfigType *>(ptype.get());
103110

@@ -274,6 +281,12 @@ Value ApiListener::ConfigDeleteObjectAPIHandler(const MessageOrigin::Ptr& origin
274281

275282
ConfigObject::Ptr object = ctype->GetObject(objName);
276283

284+
if (!object) {
285+
Log(LogNotice, "ApiListener")
286+
<< "Could not delete non-existent object '" << objName << "' with type '" << params->Get("type") << "'.";
287+
return Empty;
288+
}
289+
277290
// Check if the deletion is for an older object version
278291
double objVersion = params->Get("version");
279292
if (object && objVersion < object->GetVersion()) {
@@ -287,6 +300,11 @@ Value ApiListener::ConfigDeleteObjectAPIHandler(const MessageOrigin::Ptr& origin
287300
return Empty;
288301
}
289302

303+
/* We need to store the deletion timestamp even if no object exists, to protect against
304+
* endpoints that try to sync back objects that would have been deleted by this message.
305+
*/
306+
listener->UpdateRuntimeObjectDeletionTs(objType, objName, objVersion);
307+
290308
if (!object) {
291309
Log(LogNotice, "ApiListener")
292310
<< "Could not delete non-existent object '" << objName << "' with type '" << params->Get("type") << "'.";
@@ -450,6 +468,24 @@ void ApiListener::DeleteConfigObject(const ConfigObject::Ptr& object, const Mess
450468
if (object->GetPackage() != "_api")
451469
return;
452470

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

474510
params->Set("name", object->GetName());
475511
params->Set("type", object->GetReflectionType()->GetName());
476-
params->Set("version", object->GetVersion());
512+
params->Set("version", deletionTime);
477513

478514

479515
#ifdef I2_DEBUG
@@ -521,3 +557,47 @@ void ApiListener::SendRuntimeConfigObjects(const JsonRpcConnection::Ptr& aclient
521557
Log(LogInformation, "ApiListener")
522558
<< "Finished syncing runtime objects to endpoint '" << endpoint->GetName() << "'.";
523559
}
560+
561+
static String MakeDeletionTimestampKey(const String& typeName, const String& objName)
562+
{
563+
return typeName + ":" + objName;
564+
}
565+
566+
/**
567+
* Get the timestamp at which the object has been most recently deleted.
568+
*
569+
* @param typeName The name of the tyope of the object
570+
* @param objName The name of the object
571+
* @return the timestamp of the object, or 0 if there was no entry.
572+
*/
573+
double ApiListener::GetRuntimeObjectDeletionTs(const String& typeName, const String& objName)
574+
{
575+
auto dict = GetDeletedRuntimeObjects();
576+
auto ts = dict->Get(MakeDeletionTimestampKey(typeName, objName));
577+
if (ts.IsNumber()) {
578+
return ts.Get<double>();
579+
}
580+
return 0;
581+
}
582+
583+
/**
584+
* Updates the deletion timestamp for the object.
585+
*
586+
* The timestamp will not be changed in case a newer timestamp has already been stored
587+
* for the object.
588+
*
589+
* @param typeName The name of the tyope of the object
590+
* @param objName The name of the object
591+
* @param ts the timestamp
592+
*/
593+
bool ApiListener::UpdateRuntimeObjectDeletionTs(const String& typeName, const String& objName, double ts)
594+
{
595+
auto dict = GetDeletedRuntimeObjects();
596+
ObjectLock lock(dict);
597+
auto current = GetRuntimeObjectDeletionTs(typeName, objName);
598+
if (current > ts) {
599+
return false;
600+
}
601+
dict->Set(MakeDeletionTimestampKey(typeName, objName), ts);
602+
return true;
603+
}

lib/remote/apilistener.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ class ApiListener final : public ObjectImpl<ApiListener>
204204
void CleanupCertificateRequestsTimerHandler();
205205
void CheckApiPackageIntegrity();
206206

207+
double GetRuntimeObjectDeletionTs(const String& typeName, const String& objName);
208+
bool UpdateRuntimeObjectDeletionTs(const String& typeName, const String& objName, double ts);
209+
207210
bool AddListener(const String& node, const String& service);
208211
void StopListener();
209212
void AddConnection(const Endpoint::Ptr& endpoint);

lib/remote/apilistener.ti

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ class ApiListener : ConfigObject
6363
[no_user_modify] String identity;
6464

6565
[state, no_user_modify] Dictionary::Ptr last_failed_zones_stage_validation;
66+
67+
[state, no_user_modify] Dictionary::Ptr deleted_runtime_objects {
68+
default {{{ return new Dictionary(); }}}
69+
};
6670
};
6771

6872
}

0 commit comments

Comments
 (0)