more accurate resource pool data in aggregation reports
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -28,21 +28,22 @@ func presenceKeys(vmID, vmUUID, name string) []string {
|
||||
|
||||
// backfillLifecycleDeletionsToday looks for VMs in the lifecycle cache that are not in the current inventory,
|
||||
// have no DeletedAt, and determines their deletion time from today's hourly snapshots, optionally checking the next snapshot (next day) to confirm.
|
||||
func backfillLifecycleDeletionsToday(ctx context.Context, logger *slog.Logger, dbConn *sqlx.DB, vcenter string, snapshotTime time.Time, present map[string]InventorySnapshotRow) error {
|
||||
// It returns any hourly snapshot tables that were updated with deletion times.
|
||||
func backfillLifecycleDeletionsToday(ctx context.Context, logger *slog.Logger, dbConn *sqlx.DB, vcenter string, snapshotTime time.Time, present map[string]InventorySnapshotRow) ([]string, error) {
|
||||
dayStart := truncateDate(snapshotTime)
|
||||
dayEnd := dayStart.Add(24 * time.Hour)
|
||||
|
||||
candidates, err := loadLifecycleCandidates(ctx, dbConn, vcenter, present)
|
||||
if err != nil || len(candidates) == 0 {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tables, err := listHourlyTablesForDay(ctx, dbConn, dayStart, dayEnd)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if len(tables) == 0 {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
nextPresence := make(map[string]struct{})
|
||||
@@ -50,10 +51,12 @@ func backfillLifecycleDeletionsToday(ctx context.Context, logger *slog.Logger, d
|
||||
nextPresence = loadPresenceKeys(ctx, dbConn, nextTable, vcenter)
|
||||
}
|
||||
|
||||
for _, cand := range candidates {
|
||||
deletion, firstMiss := findDeletionInTables(ctx, dbConn, tables, vcenter, cand)
|
||||
updatedTables := make(map[string]struct{})
|
||||
for i := range candidates {
|
||||
cand := &candidates[i]
|
||||
deletion, firstMiss, lastSeenTable := findDeletionInTables(ctx, dbConn, tables, vcenter, cand)
|
||||
if deletion == 0 && len(nextPresence) > 0 && firstMiss > 0 {
|
||||
if !isPresent(nextPresence, cand) {
|
||||
if !isPresent(nextPresence, *cand) {
|
||||
// Single miss at end of day, confirmed by next-day absence.
|
||||
deletion = firstMiss
|
||||
logger.Debug("cross-day deletion inferred from next snapshot", "vcenter", vcenter, "vm_id", cand.vmID, "vm_uuid", cand.vmUUID, "name", cand.name, "deletion", deletion)
|
||||
@@ -64,10 +67,25 @@ func backfillLifecycleDeletionsToday(ctx context.Context, logger *slog.Logger, d
|
||||
logger.Warn("lifecycle backfill mark deleted failed", "vcenter", vcenter, "vm_id", cand.vmID, "vm_uuid", cand.vmUUID, "name", cand.name, "cluster", cand.cluster, "deletion", deletion, "error", err)
|
||||
continue
|
||||
}
|
||||
if lastSeenTable != "" {
|
||||
if rowsAffected, err := updateDeletionTimeInSnapshot(ctx, dbConn, lastSeenTable, vcenter, cand.vmID, cand.vmUUID, cand.name, deletion); err != nil {
|
||||
logger.Warn("lifecycle backfill failed to update hourly snapshot deletion time", "vcenter", vcenter, "vm_id", cand.vmID, "vm_uuid", cand.vmUUID, "name", cand.name, "cluster", cand.cluster, "table", lastSeenTable, "deletion", deletion, "error", err)
|
||||
} else if rowsAffected > 0 {
|
||||
updatedTables[lastSeenTable] = struct{}{}
|
||||
logger.Debug("lifecycle backfill updated hourly snapshot deletion time", "vcenter", vcenter, "vm_id", cand.vmID, "vm_uuid", cand.vmUUID, "name", cand.name, "cluster", cand.cluster, "table", lastSeenTable, "deletion", deletion)
|
||||
}
|
||||
}
|
||||
logger.Debug("lifecycle backfill applied", "vcenter", vcenter, "vm_id", cand.vmID, "vm_uuid", cand.vmUUID, "name", cand.name, "cluster", cand.cluster, "deletion", deletion)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
if len(updatedTables) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
tablesUpdated := make([]string, 0, len(updatedTables))
|
||||
for table := range updatedTables {
|
||||
tablesUpdated = append(tablesUpdated, table)
|
||||
}
|
||||
return tablesUpdated, nil
|
||||
}
|
||||
|
||||
type lifecycleCandidate struct {
|
||||
@@ -212,41 +230,47 @@ func isPresent(presence map[string]struct{}, cand lifecycleCandidate) bool {
|
||||
}
|
||||
|
||||
// findDeletionInTables walks ordered hourly tables for a vCenter and returns the first confirmed deletion time
|
||||
// (requiring two consecutive misses) plus the time of the first miss for cross-day handling.
|
||||
func findDeletionInTables(ctx context.Context, dbConn *sqlx.DB, tables []snapshotTable, vcenter string, cand lifecycleCandidate) (int64, int64) {
|
||||
// (requiring two consecutive misses), the time of the first miss for cross-day handling, and the last table where
|
||||
// the VM was seen so we can backfill deletion time into that snapshot.
|
||||
func findDeletionInTables(ctx context.Context, dbConn *sqlx.DB, tables []snapshotTable, vcenter string, cand *lifecycleCandidate) (int64, int64, string) {
|
||||
var lastSeen int64
|
||||
var lastSeenTable string
|
||||
var firstMiss int64
|
||||
for i, tbl := range tables {
|
||||
rows, err := querySnapshotRows(ctx, dbConn, tbl.Table, []string{"VmId", "VmUuid", "Name", "Cluster"}, `"Vcenter" = ? AND "VmId" = ?`, vcenter, cand.vmID)
|
||||
if err == nil {
|
||||
if rows.Next() {
|
||||
var vmId, vmUuid, name, cluster sql.NullString
|
||||
if scanErr := rows.Scan(&vmId, &vmUuid, &name, &cluster); scanErr == nil {
|
||||
lastSeen = tbl.Time
|
||||
if cand.name == "" && name.Valid {
|
||||
cand.name = name.String
|
||||
}
|
||||
if cand.cluster == "" && cluster.Valid {
|
||||
cand.cluster = cluster.String
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
seen := false
|
||||
if rows.Next() {
|
||||
var vmId, vmUuid, name, cluster sql.NullString
|
||||
if scanErr := rows.Scan(&vmId, &vmUuid, &name, &cluster); scanErr == nil {
|
||||
seen = true
|
||||
lastSeen = tbl.Time
|
||||
lastSeenTable = tbl.Table
|
||||
if cand.vmUUID == "" && vmUuid.Valid {
|
||||
cand.vmUUID = vmUuid.String
|
||||
}
|
||||
if cand.name == "" && name.Valid {
|
||||
cand.name = name.String
|
||||
}
|
||||
if cand.cluster == "" && cluster.Valid {
|
||||
cand.cluster = cluster.String
|
||||
}
|
||||
}
|
||||
rows.Close()
|
||||
}
|
||||
if lastSeen > 0 && tbl.Time > lastSeen {
|
||||
// first table after last seen -> first miss
|
||||
if seen, _ := candSeenInTable(ctx, dbConn, tbl.Table, vcenter, cand.vmID); !seen {
|
||||
firstMiss = tbl.Time
|
||||
// need two consecutive misses
|
||||
if i+1 < len(tables) {
|
||||
if seen2, _ := candSeenInTable(ctx, dbConn, tables[i+1].Table, vcenter, cand.vmID); !seen2 {
|
||||
return firstMiss, firstMiss
|
||||
}
|
||||
rows.Close()
|
||||
|
||||
if lastSeen > 0 && !seen && firstMiss == 0 {
|
||||
firstMiss = tbl.Time
|
||||
if i+1 < len(tables) {
|
||||
if seen2, _ := candSeenInTable(ctx, dbConn, tables[i+1].Table, vcenter, cand.vmID); !seen2 {
|
||||
return firstMiss, firstMiss, lastSeenTable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, firstMiss
|
||||
return 0, firstMiss, lastSeenTable
|
||||
}
|
||||
|
||||
func candSeenInTable(ctx context.Context, dbConn *sqlx.DB, table, vcenter, vmID string) (bool, error) {
|
||||
|
||||
Reference in New Issue
Block a user