From f2dbbd7b0d59383ce3574aea90a749eea3e0306f Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Thu, 10 Oct 2024 22:04:35 +0200 Subject: [PATCH] Added acknowledged field to lastseen DB This field can be used to acknowledge an update to an incoming or outgoing host entry in the lastseen DB. The field is set to `false` when a key-value pair is updated. `cf-hub` may at any time acknowledge that the update has been observed by setting the field to `true`. Ticket: ENT-11838 Changelog: Title Signed-off-by: Lars Erik Wik --- cf-check/db_structs.h | 1 + cf-check/dump.c | 2 + libpromises/dbm_migration_lastseen.c | 94 ++++++++++++++++++++++++++++ libpromises/lastseen.c | 5 +- libpromises/lastseen.h | 1 + tests/unit/lastseen_test.c | 2 + 6 files changed, 104 insertions(+), 1 deletion(-) diff --git a/cf-check/db_structs.h b/cf-check/db_structs.h index 0ca3d23a08..17ba14e73b 100644 --- a/cf-check/db_structs.h +++ b/cf-check/db_structs.h @@ -17,6 +17,7 @@ // Struct used for quality entries in /var/cfengine/state/cf_lastseen.lmdb: typedef struct { + bool acknowledged; time_t lastseen; QPoint Q; } KeyHostSeen; // Keep in sync with lastseen.h diff --git a/cf-check/dump.c b/cf-check/dump.c index 6ddc89cad6..b00bf5a29a 100644 --- a/cf-check/dump.c +++ b/cf-check/dump.c @@ -107,6 +107,7 @@ static void print_struct_lastseen_quality( memcpy(&quality, value.mv_data, sizeof(quality)); const time_t lastseen = quality.lastseen; const QPoint Q = quality.Q; + bool acknowledged = quality.acknowledged; JsonElement *q_json = JsonObjectCreate(4); JsonObjectAppendReal(q_json, "q", Q.q); @@ -115,6 +116,7 @@ static void print_struct_lastseen_quality( JsonObjectAppendReal(q_json, "dq", Q.dq); JsonElement *top_json = JsonObjectCreate(2); + JsonObjectAppendBool(top_json, "acknowledged", acknowledged); JsonObjectAppendInteger(top_json, "lastseen", lastseen); JsonObjectAppendObject(top_json, "Q", q_json); diff --git a/libpromises/dbm_migration_lastseen.c b/libpromises/dbm_migration_lastseen.c index 9590efea6d..77c4a742ef 100644 --- a/libpromises/dbm_migration_lastseen.c +++ b/libpromises/dbm_migration_lastseen.c @@ -34,6 +34,12 @@ typedef struct double var; } QPoint0; +typedef struct +{ + time_t lastseen; + QPoint Q; // Average time between connections (rolling weighted average) +} KeyHostSeen1; + #define QPOINT0_OFFSET 128 /* @@ -182,8 +188,96 @@ static bool LastseenMigrationVersion0(DBHandle *db) return !errors; } +static bool LastseenMigrationVersion1(DBHandle *db) +{ + DBCursor *cursor; + if (!NewDBCursor(db, &cursor)) + { + Log(LOG_LEVEL_ERR, + "Unable to create database cursor during lastseen migration"); + return false; + } + + char *key; + void *value; + int key_size, value_size; + + // Iterate through all key-value pairs + while (NextDB(cursor, &key, &key_size, &value, &value_size)) + { + if (key_size == 0) + { + Log(LOG_LEVEL_WARNING, + "Found zero-length key during lastseen migration"); + continue; + } + + // Only look for old KeyHostSeen entries + if (key[0] != 'q') + { + // Warn about completely unexpected keys + if ((key[0] != 'k') && (key[0] != 'a')) + { + Log(LOG_LEVEL_WARNING, + "Found unexpected key '%s' during lastseen migration", + key); + } + continue; + } + + KeyHostSeen1 *old_value = value; + KeyHostSeen new_value = { + .acknowledged = true, // We don't know, assume yes + .lastseen = old_value->lastseen, + .Q = old_value->Q, + }; + + if (!DBCursorDeleteEntry(cursor)) + { + Log(LOG_LEVEL_ERR, + "Unable to delete version 1 of entry for key '%s' during lastseen migration", + key); + if (DeleteDBCursor(cursor) == false) + { + Log(LOG_LEVEL_ERR, + "Unable to close cursor during lastseen migration"); + } + return false; + } + + if (!DBCursorWriteEntry(db, &new_value, sizeof(new_value))) + { + Log(LOG_LEVEL_INFO, + "Unable to write version 2 of entry for key '%s' during lastseen migration", + key); + if (DeleteDBCursor(cursor) == false) + { + Log(LOG_LEVEL_ERR, + "Unable to close cursor during lastseen migration"); + } + return false; + } + } + + if (!DeleteDBCursor(cursor)) + { + Log(LOG_LEVEL_ERR, "Unable to close cursor during lastseen migration"); + return false; + } + + if (!WriteDB(db, "version", "2", sizeof("2"))) + { + Log(LOG_LEVEL_ERR, "Failed to update version number durling lastseen migration"); + return false; + } + + Log(LOG_LEVEL_INFO, "Migrated lastseen database from version 1 to 2"); + return true; +} + const DBMigrationFunction dbm_migration_plan_lastseen[] = { LastseenMigrationVersion0, + LastseenMigrationVersion1, NULL }; diff --git a/libpromises/lastseen.c b/libpromises/lastseen.c index 0dc7bdc295..8863239e8a 100644 --- a/libpromises/lastseen.c +++ b/libpromises/lastseen.c @@ -121,7 +121,10 @@ void UpdateLastSawHost(const char *hostkey, const char *address, char quality_key[CF_BUFSIZE]; snprintf(quality_key, CF_BUFSIZE, "q%c%s", incoming ? 'i' : 'o', hostkey); - KeyHostSeen newq = { .lastseen = timestamp }; + KeyHostSeen newq = { + .acknowledged = false, + .lastseen = timestamp, + }; KeyHostSeen q; if (ReadDB(db, quality_key, &q, sizeof(q))) diff --git a/libpromises/lastseen.h b/libpromises/lastseen.h index 7fb4878ba9..6b1823187b 100644 --- a/libpromises/lastseen.h +++ b/libpromises/lastseen.h @@ -29,6 +29,7 @@ typedef struct { + bool acknowledged; // True when acknowledged by cf-hub, false when updated time_t lastseen; QPoint Q; // Average time between connections (rolling weighted average) } KeyHostSeen; diff --git a/tests/unit/lastseen_test.c b/tests/unit/lastseen_test.c index 0344a2e59b..0cd28a31c7 100644 --- a/tests/unit/lastseen_test.c +++ b/tests/unit/lastseen_test.c @@ -70,6 +70,7 @@ static void test_newentry(void) KeyHostSeen q; assert_int_equal(ReadDB(db, "qiSHA-12345", &q, sizeof(q)), true); + assert_false(q.acknowledged); assert_int_equal(q.lastseen, 666); assert_double_close(q.Q.q, 0.0); assert_double_close(q.Q.dq, 0.0); @@ -102,6 +103,7 @@ static void test_update(void) KeyHostSeen q; assert_int_equal(ReadDB(db, "qiSHA-12345", &q, sizeof(q)), true); + assert_false(q.acknowledged); assert_int_equal(q.lastseen, 1110); assert_double_close(q.Q.q, 555.0); assert_double_close(q.Q.dq, 555.0);