This commit is contained in:
@@ -503,128 +503,6 @@ func normalizeResourcePool(value string) string {
|
||||
return trimmed
|
||||
}
|
||||
|
||||
// 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.
|
||||
func backfillLifecycleDeletionsToday(ctx context.Context, logger *slog.Logger, dbConn *sqlx.DB, vcenter string, snapshotTime time.Time, present map[string]InventorySnapshotRow) error {
|
||||
dayStart := truncateDate(snapshotTime)
|
||||
dayEnd := dayStart.Add(24 * time.Hour)
|
||||
|
||||
// Lifecycle entries missing DeletedAt.
|
||||
queryLifecycle := `
|
||||
SELECT "VmId","VmUuid","Name","Cluster"
|
||||
FROM vm_lifecycle_cache
|
||||
WHERE "Vcenter" = ? AND ("DeletedAt" IS NULL OR "DeletedAt" = 0)
|
||||
`
|
||||
rows, err := dbConn.QueryxContext(ctx, queryLifecycle, vcenter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
type candidate struct {
|
||||
vmID string
|
||||
vmUUID string
|
||||
name string
|
||||
cluster string
|
||||
}
|
||||
var cands []candidate
|
||||
for rows.Next() {
|
||||
var vmID, vmUUID, name, cluster sql.NullString
|
||||
if scanErr := rows.Scan(&vmID, &vmUUID, &name, &cluster); scanErr != nil {
|
||||
continue
|
||||
}
|
||||
if vmID.String == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := present[vmID.String]; ok {
|
||||
continue // still present, skip
|
||||
}
|
||||
cands = append(cands, candidate{
|
||||
vmID: vmID.String,
|
||||
vmUUID: vmUUID.String,
|
||||
name: name.String,
|
||||
cluster: cluster.String,
|
||||
})
|
||||
}
|
||||
|
||||
if len(cands) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get today's hourly tables.
|
||||
query := `
|
||||
SELECT table_name, snapshot_time
|
||||
FROM snapshot_registry
|
||||
WHERE snapshot_type = 'hourly' AND snapshot_time >= ? AND snapshot_time < ?
|
||||
ORDER BY snapshot_time ASC
|
||||
`
|
||||
query = sqlx.Rebind(sqlx.BindType(dbConn.DriverName()), query)
|
||||
var tables []struct {
|
||||
Table string `db:"table_name"`
|
||||
Time int64 `db:"snapshot_time"`
|
||||
}
|
||||
rowsTables, err := dbConn.QueryxContext(ctx, query, dayStart.Unix(), dayEnd.Unix())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rowsTables.Close()
|
||||
for rowsTables.Next() {
|
||||
var t struct {
|
||||
Table string `db:"table_name"`
|
||||
Time int64 `db:"snapshot_time"`
|
||||
}
|
||||
if err := rowsTables.StructScan(&t); err != nil {
|
||||
continue
|
||||
}
|
||||
tables = append(tables, t)
|
||||
}
|
||||
if len(tables) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, cand := range cands {
|
||||
var lastSeen int64
|
||||
var deletion int64
|
||||
logger.Debug("lifecycle backfill candidate", "vcenter", vcenter, "vm_id", cand.vmID, "vm_uuid", cand.vmUUID, "name", cand.name, "cluster", cand.cluster, "tables", len(tables))
|
||||
for i, tbl := range tables {
|
||||
if err := db.ValidateTableName(tbl.Table); err != nil {
|
||||
continue
|
||||
}
|
||||
q := fmt.Sprintf(`SELECT "Name","Cluster" FROM %s WHERE "Vcenter" = ? AND "VmId" = ? LIMIT 1`, tbl.Table)
|
||||
q = sqlx.Rebind(sqlx.BindType(dbConn.DriverName()), q)
|
||||
var name, cluster sql.NullString
|
||||
err := dbConn.QueryRowxContext(ctx, q, vcenter, cand.vmID).Scan(&name, &cluster)
|
||||
if err == nil {
|
||||
lastSeen = tbl.Time
|
||||
if cand.name == "" && name.Valid {
|
||||
cand.name = name.String
|
||||
}
|
||||
if cand.cluster == "" && cluster.Valid {
|
||||
cand.cluster = cluster.String
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Not found in this table; if previously seen today, mark deletion at this snapshot time.
|
||||
if lastSeen > 0 {
|
||||
deletion = tbl.Time
|
||||
break
|
||||
}
|
||||
// If never seen today and we're at the last table, mark deletion at current snapshot time.
|
||||
if i == len(tables)-1 {
|
||||
deletion = tbl.Time
|
||||
}
|
||||
}
|
||||
if deletion > 0 {
|
||||
if err := db.MarkVmDeletedWithDetails(ctx, dbConn, vcenter, cand.vmID, cand.vmUUID, cand.name, cand.cluster, deletion); err != nil {
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (c *CronTask) reportsDir() string {
|
||||
if c.Settings != nil && c.Settings.Values != nil {
|
||||
if dir := strings.TrimSpace(c.Settings.Values.Settings.ReportsDir); dir != "" {
|
||||
@@ -1099,8 +977,8 @@ func (c *CronTask) captureHourlySnapshotForVcenter(ctx context.Context, startTim
|
||||
moreMissing := c.markMissingFromPrevious(ctx, dbConn, prevTableName, url, startTime, presentSnapshots, presentByUuid, presentByName, inventoryByVmID, inventoryByUuid, inventoryByName)
|
||||
missingCount += moreMissing
|
||||
// Guard against gaps: if previous snapshot is much older than expected, skip "new" detection to avoid false positives when an hourly run was missed.
|
||||
snapshotPeriod := durationFromSeconds(c.Settings.Values.Settings.VcenterInventorySnapshotSeconds, time.Hour).Seconds()
|
||||
if prevSnapshotTime > 0 && startTime.Unix()-prevSnapshotTime > int64(snapshotPeriod*2) {
|
||||
expectedSeconds := int64(durationFromSeconds(c.Settings.Values.Settings.VcenterInventorySnapshotSeconds, time.Hour).Seconds())
|
||||
if HasSnapshotGap(prevSnapshotTime, startTime.Unix(), expectedSeconds) {
|
||||
c.Logger.Info("skipping new-VM detection due to gap between snapshots", "prev_table", prevTableName, "prev_snapshot_unix", prevSnapshotTime, "current_snapshot_unix", startTime.Unix())
|
||||
} else {
|
||||
newCount = countNewFromPrevious(ctx, dbConn, prevTableName, url, presentSnapshots)
|
||||
|
||||
Reference in New Issue
Block a user