|
|
|
@@ -315,21 +315,59 @@ LIMIT 1
|
|
|
|
|
}
|
|
|
|
|
nextSnapshotRows.Close()
|
|
|
|
|
}
|
|
|
|
|
nextPresence := make(map[string]struct{})
|
|
|
|
|
|
|
|
|
|
// Build per-vCenter snapshot timelines from observed VM samples so deletion
|
|
|
|
|
// inference is only based on times where that vCenter actually reported data.
|
|
|
|
|
vcenterTimeSet := make(map[string]map[int64]struct{}, 8)
|
|
|
|
|
for _, v := range aggMap {
|
|
|
|
|
if v.key.Vcenter == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
set := vcenterTimeSet[v.key.Vcenter]
|
|
|
|
|
if set == nil {
|
|
|
|
|
set = make(map[int64]struct{}, len(v.seen))
|
|
|
|
|
vcenterTimeSet[v.key.Vcenter] = set
|
|
|
|
|
}
|
|
|
|
|
for t := range v.seen {
|
|
|
|
|
if t > 0 {
|
|
|
|
|
set[t] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
vcenterSnapTimes := make(map[string][]int64, len(vcenterTimeSet))
|
|
|
|
|
for vcenter, set := range vcenterTimeSet {
|
|
|
|
|
times := make([]int64, 0, len(set))
|
|
|
|
|
for t := range set {
|
|
|
|
|
times = append(times, t)
|
|
|
|
|
}
|
|
|
|
|
sort.Slice(times, func(i, j int) bool { return times[i] < times[j] })
|
|
|
|
|
vcenterSnapTimes[vcenter] = times
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nextPresenceByVcenter := make(map[string]map[string]struct{}, 8)
|
|
|
|
|
if nextSnapshotTable != "" && db.TableExists(ctx, dbConn, nextSnapshotTable) {
|
|
|
|
|
rows, err := querySnapshotRows(ctx, dbConn, nextSnapshotTable, []string{"VmId", "VmUuid", "Name"}, `"Vcenter" = ?`, c.Settings.Values.Settings.VcenterAddresses[0])
|
|
|
|
|
rows, err := querySnapshotRows(ctx, dbConn, nextSnapshotTable, []string{"Vcenter", "VmId", "VmUuid", "Name"}, "")
|
|
|
|
|
if err == nil {
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
var vcenter string
|
|
|
|
|
var vmId, vmUuid, name sql.NullString
|
|
|
|
|
if err := rows.Scan(&vmId, &vmUuid, &name); err == nil {
|
|
|
|
|
if vmId.Valid {
|
|
|
|
|
nextPresence["id:"+vmId.String] = struct{}{}
|
|
|
|
|
if err := rows.Scan(&vcenter, &vmId, &vmUuid, &name); err == nil {
|
|
|
|
|
if strings.TrimSpace(vcenter) == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if vmUuid.Valid {
|
|
|
|
|
nextPresence["uuid:"+vmUuid.String] = struct{}{}
|
|
|
|
|
vcPresence := nextPresenceByVcenter[vcenter]
|
|
|
|
|
if vcPresence == nil {
|
|
|
|
|
vcPresence = make(map[string]struct{}, 1024)
|
|
|
|
|
nextPresenceByVcenter[vcenter] = vcPresence
|
|
|
|
|
}
|
|
|
|
|
if name.Valid {
|
|
|
|
|
nextPresence["name:"+name.String] = struct{}{}
|
|
|
|
|
if vmId.Valid && strings.TrimSpace(vmId.String) != "" {
|
|
|
|
|
vcPresence["id:"+strings.TrimSpace(vmId.String)] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
if vmUuid.Valid && strings.TrimSpace(vmUuid.String) != "" {
|
|
|
|
|
vcPresence["uuid:"+strings.TrimSpace(vmUuid.String)] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
if name.Valid && strings.TrimSpace(name.String) != "" {
|
|
|
|
|
vcPresence["name:"+strings.ToLower(strings.TrimSpace(name.String))] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -337,23 +375,24 @@ LIMIT 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var maxSnap int64
|
|
|
|
|
if len(snapTimes) > 0 {
|
|
|
|
|
maxSnap = snapTimes[len(snapTimes)-1]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inferredDeletions := 0
|
|
|
|
|
for _, v := range aggMap {
|
|
|
|
|
if v.deletion != 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
vcSnapTimes := vcenterSnapTimes[v.key.Vcenter]
|
|
|
|
|
// Deletion inference needs meaningful per-vCenter continuity.
|
|
|
|
|
if len(vcSnapTimes) < 3 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
vcMaxSnap := vcSnapTimes[len(vcSnapTimes)-1]
|
|
|
|
|
// Infer deletion only after seeing at least two consecutive absent snapshots after lastSeen.
|
|
|
|
|
if maxSnap > 0 && len(v.seen) > 0 && v.lastSeen < maxSnap {
|
|
|
|
|
c.Logger.Debug("inferring deletion window", "vm_id", v.key.VmId, "vm_uuid", v.key.VmUuid, "name", v.key.Name, "last_seen", v.lastSeen, "snapshots", len(snapTimes))
|
|
|
|
|
if vcMaxSnap > 0 && len(v.seen) > 0 && v.lastSeen < vcMaxSnap {
|
|
|
|
|
c.Logger.Debug("inferring deletion window", "vcenter", v.key.Vcenter, "vm_id", v.key.VmId, "vm_uuid", v.key.VmUuid, "name", v.key.Name, "last_seen", v.lastSeen, "snapshots", len(vcSnapTimes))
|
|
|
|
|
}
|
|
|
|
|
consecutiveMisses := 0
|
|
|
|
|
firstMiss := int64(0)
|
|
|
|
|
for _, t := range snapTimes {
|
|
|
|
|
for _, t := range vcSnapTimes {
|
|
|
|
|
if t <= v.lastSeen {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
@@ -374,18 +413,19 @@ LIMIT 1
|
|
|
|
|
}
|
|
|
|
|
if v.deletion == 0 && firstMiss > 0 {
|
|
|
|
|
// Not enough consecutive misses within the day; try to use the first snapshot of the next day to confirm.
|
|
|
|
|
nextPresence := nextPresenceByVcenter[v.key.Vcenter]
|
|
|
|
|
if nextSnapshotTable != "" && len(nextPresence) > 0 {
|
|
|
|
|
_, presentByID := nextPresence["id:"+v.key.VmId]
|
|
|
|
|
_, presentByUUID := nextPresence["uuid:"+v.key.VmUuid]
|
|
|
|
|
_, presentByName := nextPresence["name:"+v.key.Name]
|
|
|
|
|
_, presentByID := nextPresence["id:"+strings.TrimSpace(v.key.VmId)]
|
|
|
|
|
_, presentByUUID := nextPresence["uuid:"+strings.TrimSpace(v.key.VmUuid)]
|
|
|
|
|
_, presentByName := nextPresence["name:"+strings.ToLower(strings.TrimSpace(v.key.Name))]
|
|
|
|
|
if !presentByID && !presentByUUID && !presentByName {
|
|
|
|
|
v.deletion = firstMiss
|
|
|
|
|
inferredDeletions++
|
|
|
|
|
c.Logger.Debug("cross-day deletion inferred from next snapshot", "vm_id", v.key.VmId, "vm_uuid", v.key.VmUuid, "name", v.key.Name, "deletion", firstMiss, "next_table", nextSnapshotTable)
|
|
|
|
|
c.Logger.Debug("cross-day deletion inferred from next snapshot", "vcenter", v.key.Vcenter, "vm_id", v.key.VmId, "vm_uuid", v.key.VmUuid, "name", v.key.Name, "deletion", firstMiss, "next_table", nextSnapshotTable)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if v.deletion == 0 {
|
|
|
|
|
c.Logger.Debug("pending deletion inference (insufficient consecutive misses)", "vm_id", v.key.VmId, "vm_uuid", v.key.VmUuid, "name", v.key.Name, "last_seen", v.lastSeen, "first_missing_snapshot", firstMiss)
|
|
|
|
|
c.Logger.Debug("pending deletion inference (insufficient consecutive misses)", "vcenter", v.key.Vcenter, "vm_id", v.key.VmId, "vm_uuid", v.key.VmUuid, "name", v.key.Name, "last_seen", v.lastSeen, "first_missing_snapshot", firstMiss)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|