Implement targeted VM property refresh and backfill logic for snapshot rows
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:
@@ -690,6 +690,33 @@ func snapshotFromVM(ctx context.Context, dbConn *sqlx.DB, vmObject *mo.VirtualMa
|
||||
return InventorySnapshotRow{}, fmt.Errorf("missing VM object")
|
||||
}
|
||||
|
||||
// Rarely, bulk property retrieval can return partial VM objects (for example, nil Config).
|
||||
// Do a targeted per-VM refresh before building the snapshot row to avoid writing sparse rows.
|
||||
if vmObject.Config == nil || vmObject.ResourcePool == nil || vmObject.Parent == nil || vmObject.Runtime.Host == nil || vmObject.Runtime.PowerState == "" {
|
||||
if refreshed, err := vc.GetVMWithSnapshotPropsByRef(vmObject.Reference()); err == nil && refreshed != nil {
|
||||
if vmObject.Name == "" && refreshed.Name != "" {
|
||||
vmObject.Name = refreshed.Name
|
||||
}
|
||||
if vmObject.Parent == nil && refreshed.Parent != nil {
|
||||
vmObject.Parent = refreshed.Parent
|
||||
}
|
||||
if vmObject.Config == nil && refreshed.Config != nil {
|
||||
vmObject.Config = refreshed.Config
|
||||
}
|
||||
if vmObject.ResourcePool == nil && refreshed.ResourcePool != nil {
|
||||
vmObject.ResourcePool = refreshed.ResourcePool
|
||||
}
|
||||
if vmObject.Runtime.Host == nil && refreshed.Runtime.Host != nil {
|
||||
vmObject.Runtime.Host = refreshed.Runtime.Host
|
||||
}
|
||||
if vmObject.Runtime.PowerState == "" && refreshed.Runtime.PowerState != "" {
|
||||
vmObject.Runtime.PowerState = refreshed.Runtime.PowerState
|
||||
}
|
||||
} else if vc.Logger != nil {
|
||||
vc.Logger.Warn("failed targeted VM property refresh for snapshot row", "vm_id", vmObject.Reference().Value, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
row := InventorySnapshotRow{
|
||||
Name: vmObject.Name,
|
||||
Vcenter: vc.Vurl,
|
||||
@@ -903,9 +930,144 @@ func snapshotFromVM(ctx context.Context, dbConn *sqlx.DB, vmObject *mo.VirtualMa
|
||||
}
|
||||
}
|
||||
|
||||
if dbConn != nil && needsSnapshotBackfill(row) {
|
||||
if backfillSnapshotRowFromHourlyCache(ctx, dbConn, &row) && vc.Logger != nil {
|
||||
vc.Logger.Debug("backfilled sparse VM snapshot row from hourly cache", "vm_id", row.VmId.String, "name", row.Name, "vcenter", row.Vcenter)
|
||||
}
|
||||
}
|
||||
if row.SrmPlaceholder == "" {
|
||||
row.SrmPlaceholder = "FALSE"
|
||||
}
|
||||
if needsSnapshotBackfill(row) && vc.Logger != nil {
|
||||
vc.Logger.Warn("snapshot row remains sparse after fallbacks",
|
||||
"vm_id", row.VmId.String,
|
||||
"name", row.Name,
|
||||
"vcenter", row.Vcenter,
|
||||
"has_creation_time", row.CreationTime.Valid,
|
||||
"has_cluster", row.Cluster.Valid,
|
||||
"has_disk", row.ProvisionedDisk.Valid,
|
||||
"has_vcpu", row.VcpuCount.Valid,
|
||||
"has_ram", row.RamGB.Valid,
|
||||
"has_vm_uuid", row.VmUuid.Valid,
|
||||
)
|
||||
}
|
||||
|
||||
return row, nil
|
||||
}
|
||||
|
||||
func needsSnapshotBackfill(row InventorySnapshotRow) bool {
|
||||
return !row.CreationTime.Valid ||
|
||||
!row.ProvisionedDisk.Valid ||
|
||||
!row.VcpuCount.Valid ||
|
||||
!row.RamGB.Valid ||
|
||||
!row.Cluster.Valid ||
|
||||
!row.Datacenter.Valid ||
|
||||
row.SrmPlaceholder == "" ||
|
||||
!row.VmUuid.Valid
|
||||
}
|
||||
|
||||
func backfillSnapshotRowFromHourlyCache(ctx context.Context, dbConn *sqlx.DB, row *InventorySnapshotRow) bool {
|
||||
if row == nil || dbConn == nil {
|
||||
return false
|
||||
}
|
||||
if strings.TrimSpace(row.Vcenter) == "" || strings.TrimSpace(row.VmId.String) == "" {
|
||||
return false
|
||||
}
|
||||
if !db.TableExists(ctx, dbConn, "vm_hourly_stats") {
|
||||
return false
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
"CreationTime",
|
||||
"ResourcePool",
|
||||
"Datacenter",
|
||||
"Cluster",
|
||||
"Folder",
|
||||
"ProvisionedDisk",
|
||||
"VcpuCount",
|
||||
"RamGB",
|
||||
"IsTemplate",
|
||||
"PoweredOn",
|
||||
"SrmPlaceholder",
|
||||
"VmUuid"
|
||||
FROM vm_hourly_stats
|
||||
WHERE "Vcenter" = ? AND "VmId" = ?
|
||||
ORDER BY "SnapshotTime" DESC
|
||||
LIMIT 1`
|
||||
query = sqlx.Rebind(sqlx.BindType(dbConn.DriverName()), query)
|
||||
|
||||
var cached struct {
|
||||
CreationTime sql.NullInt64 `db:"CreationTime"`
|
||||
ResourcePool sql.NullString `db:"ResourcePool"`
|
||||
Datacenter sql.NullString `db:"Datacenter"`
|
||||
Cluster sql.NullString `db:"Cluster"`
|
||||
Folder sql.NullString `db:"Folder"`
|
||||
ProvisionedDisk sql.NullFloat64 `db:"ProvisionedDisk"`
|
||||
VcpuCount sql.NullInt64 `db:"VcpuCount"`
|
||||
RamGB sql.NullInt64 `db:"RamGB"`
|
||||
IsTemplate sql.NullString `db:"IsTemplate"`
|
||||
PoweredOn sql.NullString `db:"PoweredOn"`
|
||||
SrmPlaceholder sql.NullString `db:"SrmPlaceholder"`
|
||||
VmUuid sql.NullString `db:"VmUuid"`
|
||||
}
|
||||
if err := dbConn.GetContext(ctx, &cached, query, row.Vcenter, row.VmId.String); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
changed := false
|
||||
if !row.CreationTime.Valid && cached.CreationTime.Valid {
|
||||
row.CreationTime = cached.CreationTime
|
||||
changed = true
|
||||
}
|
||||
if (!row.ResourcePool.Valid || row.ResourcePool.String == "") && cached.ResourcePool.Valid {
|
||||
pool := normalizeResourcePool(cached.ResourcePool.String)
|
||||
row.ResourcePool = sql.NullString{String: pool, Valid: pool != ""}
|
||||
changed = true
|
||||
}
|
||||
if (!row.Datacenter.Valid || row.Datacenter.String == "") && cached.Datacenter.Valid {
|
||||
row.Datacenter = cached.Datacenter
|
||||
changed = true
|
||||
}
|
||||
if (!row.Cluster.Valid || row.Cluster.String == "") && cached.Cluster.Valid {
|
||||
row.Cluster = cached.Cluster
|
||||
changed = true
|
||||
}
|
||||
if (!row.Folder.Valid || row.Folder.String == "") && cached.Folder.Valid {
|
||||
row.Folder = cached.Folder
|
||||
changed = true
|
||||
}
|
||||
if !row.ProvisionedDisk.Valid && cached.ProvisionedDisk.Valid {
|
||||
row.ProvisionedDisk = cached.ProvisionedDisk
|
||||
changed = true
|
||||
}
|
||||
if !row.VcpuCount.Valid && cached.VcpuCount.Valid {
|
||||
row.VcpuCount = cached.VcpuCount
|
||||
changed = true
|
||||
}
|
||||
if !row.RamGB.Valid && cached.RamGB.Valid {
|
||||
row.RamGB = cached.RamGB
|
||||
changed = true
|
||||
}
|
||||
if row.IsTemplate == "" && cached.IsTemplate.Valid {
|
||||
row.IsTemplate = boolStringFromInterface(cached.IsTemplate.String)
|
||||
changed = true
|
||||
}
|
||||
if row.PoweredOn == "" && cached.PoweredOn.Valid {
|
||||
row.PoweredOn = boolStringFromInterface(cached.PoweredOn.String)
|
||||
changed = true
|
||||
}
|
||||
if row.SrmPlaceholder == "" && cached.SrmPlaceholder.Valid {
|
||||
row.SrmPlaceholder = boolStringFromInterface(cached.SrmPlaceholder.String)
|
||||
changed = true
|
||||
}
|
||||
if !row.VmUuid.Valid && cached.VmUuid.Valid {
|
||||
row.VmUuid = cached.VmUuid
|
||||
changed = true
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
func snapshotFromInventory(inv queries.Inventory, snapshotTime time.Time) InventorySnapshotRow {
|
||||
return InventorySnapshotRow{
|
||||
InventoryId: sql.NullInt64{Int64: inv.Iid, Valid: inv.Iid > 0},
|
||||
|
||||
Reference in New Issue
Block a user