From ff783fb45a7f36cfad0c7b2edd7abf558432843f Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Wed, 28 Jan 2026 15:19:10 +1100 Subject: [PATCH] still working on creation/deletion times --- db/helpers.go | 33 ++++++++++++++++++++++++++-- internal/tasks/dailyAggregate.go | 6 +++-- internal/tasks/inventorySnapshots.go | 4 ++-- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/db/helpers.go b/db/helpers.go index a9b04fd..b70264b 100644 --- a/db/helpers.go +++ b/db/helpers.go @@ -557,6 +557,37 @@ ON CONFLICT ("Vcenter","VmId","VmUuid") DO UPDATE SET return err } +// MarkVmDeletedFromEvent updates lifecycle cache with a deletion timestamp from vCenter events. +// Event times should override snapshot-derived timestamps, even if later. +func MarkVmDeletedFromEvent(ctx context.Context, dbConn *sqlx.DB, vcenter, vmID, vmUUID, name, cluster string, deletedAt int64) error { + if err := EnsureVmLifecycleCache(ctx, dbConn); err != nil { + return err + } + driver := strings.ToLower(dbConn.DriverName()) + bindType := sqlx.BindType(driver) + + query := ` +INSERT INTO vm_lifecycle_cache ("Vcenter","VmId","VmUuid","Name","Cluster","DeletedAt","FirstSeen","LastSeen") +VALUES (?, ?, ?, ?, ?, ?, ?, ?) +ON CONFLICT ("Vcenter","VmId","VmUuid") DO UPDATE SET + "DeletedAt"=CASE + WHEN EXCLUDED."DeletedAt" IS NOT NULL AND EXCLUDED."DeletedAt" > 0 THEN EXCLUDED."DeletedAt" + ELSE vm_lifecycle_cache."DeletedAt" + END, + "LastSeen"=COALESCE(vm_lifecycle_cache."LastSeen", EXCLUDED."LastSeen"), + "FirstSeen"=COALESCE(vm_lifecycle_cache."FirstSeen", EXCLUDED."FirstSeen"), + "Name"=COALESCE(NULLIF(vm_lifecycle_cache."Name", ''), EXCLUDED."Name"), + "Cluster"=COALESCE(NULLIF(vm_lifecycle_cache."Cluster", ''), EXCLUDED."Cluster") +` + query = sqlx.Rebind(bindType, query) + args := []interface{}{vcenter, vmID, vmUUID, name, cluster, deletedAt, deletedAt, deletedAt} + _, err := dbConn.ExecContext(ctx, query, args...) + if err != nil { + slog.Warn("lifecycle delete event exec failed", "vcenter", vcenter, "vm_id", vmID, "vm_uuid", vmUUID, "driver", driver, "args_len", len(args), "args", fmt.Sprint(args), "query", strings.TrimSpace(query), "error", err) + } + return err +} + // MarkVmDeleted updates lifecycle cache with a deletion timestamp (legacy signature). func MarkVmDeleted(ctx context.Context, dbConn *sqlx.DB, vcenter, vmID, vmUUID string, deletedAt int64) error { return MarkVmDeletedWithDetails(ctx, dbConn, vcenter, vmID, vmUUID, "", "", deletedAt) @@ -1609,7 +1640,6 @@ UPDATE %s dst SET "CreationTime" = CASE WHEN t.any_creation IS NOT NULL AND t.any_creation > 0 THEN LEAST(COALESCE(NULLIF(dst."CreationTime", 0), t.any_creation), t.any_creation) - WHEN (dst."CreationTime" IS NULL OR dst."CreationTime" = 0) AND t.first_seen IS NOT NULL AND t.first_seen > 0 THEN t.first_seen ELSE dst."CreationTime" END, "DeletionTime" = CASE @@ -1671,7 +1701,6 @@ SET ( SELECT CASE WHEN t.any_creation IS NOT NULL AND t.any_creation > 0 AND COALESCE(NULLIF(%[2]s."CreationTime", 0), t.any_creation) > t.any_creation THEN t.any_creation - WHEN (%[2]s."CreationTime" IS NULL OR %[2]s."CreationTime" = 0) AND t.first_seen IS NOT NULL AND t.first_seen > 0 THEN t.first_seen ELSE NULL END FROM enriched t diff --git a/internal/tasks/dailyAggregate.go b/internal/tasks/dailyAggregate.go index 738c705..ec4a1cc 100644 --- a/internal/tasks/dailyAggregate.go +++ b/internal/tasks/dailyAggregate.go @@ -532,8 +532,10 @@ WHERE "Vcenter" = ? AND "DeletedAt" IS NOT NULL AND "DeletedAt" > 0 AND "Deleted missed++ continue } - target.deletion = deletedAt.Int64 - applied++ + if target.deletion == 0 || deletedAt.Int64 < target.deletion { + target.deletion = deletedAt.Int64 + applied++ + } } rows.Close() if err := rows.Err(); err != nil { diff --git a/internal/tasks/inventorySnapshots.go b/internal/tasks/inventorySnapshots.go index 9a66d17..1eb34f7 100644 --- a/internal/tasks/inventorySnapshots.go +++ b/internal/tasks/inventorySnapshots.go @@ -1134,7 +1134,7 @@ func (c *CronTask) captureHourlySnapshotForVcenter(ctx context.Context, startTim }); err != nil { log.Warn("failed to update inventory deletion time from event", "vm_id", cand.vmID, "vm_uuid", cand.vmUUID, "vcenter", url, "error", err) } - if err := db.MarkVmDeletedWithDetails(ctx, dbConn, url, cand.vmID, cand.vmUUID, cand.name, cand.cluster, t.Unix()); err != nil { + if err := db.MarkVmDeletedFromEvent(ctx, dbConn, url, cand.vmID, cand.vmUUID, cand.name, cand.cluster, t.Unix()); err != nil { log.Warn("failed to refine lifecycle cache deletion time", "vm_id", cand.vmID, "vm_uuid", cand.vmUUID, "vcenter", url, "error", err) } if snapRow, snapTable, found := findVMInHourlySnapshots(ctx, dbConn, url, cand.vmID); found { @@ -1276,7 +1276,7 @@ func (c *CronTask) captureHourlySnapshotForVcenter(ctx context.Context, startTim if inv.Cluster.Valid { clusterName = inv.Cluster.String } - if err := db.MarkVmDeletedWithDetails(ctx, dbConn, url, vmID, inv.VmUuid.String, inv.Name, clusterName, t.Unix()); err != nil { + if err := db.MarkVmDeletedFromEvent(ctx, dbConn, url, vmID, inv.VmUuid.String, inv.Name, clusterName, t.Unix()); err != nil { c.Logger.Warn("count-drop: failed to refine lifecycle cache deletion time", "vm_id", vmID, "vm_uuid", inv.VmUuid, "vcenter", url, "error", err) } tableToUpdate := snapTable