|
|
|
|
@@ -5,8 +5,6 @@ import (
|
|
|
|
|
"database/sql"
|
|
|
|
|
"fmt"
|
|
|
|
|
"log/slog"
|
|
|
|
|
"os"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
"vctp/db/queries"
|
|
|
|
|
@@ -33,8 +31,8 @@ type inventorySnapshotRow struct {
|
|
|
|
|
Cluster sql.NullString
|
|
|
|
|
Folder sql.NullString
|
|
|
|
|
ProvisionedDisk sql.NullFloat64
|
|
|
|
|
InitialVcpus sql.NullInt64
|
|
|
|
|
InitialRam sql.NullInt64
|
|
|
|
|
VcpuCount sql.NullInt64
|
|
|
|
|
RamGB sql.NullInt64
|
|
|
|
|
IsTemplate string
|
|
|
|
|
PoweredOn string
|
|
|
|
|
SrmPlaceholder string
|
|
|
|
|
@@ -46,7 +44,7 @@ type inventorySnapshotRow struct {
|
|
|
|
|
// RunVcenterSnapshotHourly records hourly inventory snapshots into a daily table.
|
|
|
|
|
func (c *CronTask) RunVcenterSnapshotHourly(ctx context.Context, logger *slog.Logger) error {
|
|
|
|
|
startTime := time.Now()
|
|
|
|
|
tableName, err := dailyInventoryTableName(startTime)
|
|
|
|
|
tableName, err := hourlyInventoryTableName(startTime)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
@@ -55,6 +53,12 @@ func (c *CronTask) RunVcenterSnapshotHourly(ctx context.Context, logger *slog.Lo
|
|
|
|
|
if err := ensureDailyInventoryTable(ctx, dbConn, tableName); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err := report.EnsureSnapshotRegistry(ctx, c.Database); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err := report.RegisterSnapshot(ctx, c.Database, "hourly", tableName, startTime); err != nil {
|
|
|
|
|
c.Logger.Warn("failed to register hourly snapshot", "error", err, "table", tableName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// reload settings in case vcenter list has changed
|
|
|
|
|
c.Settings.ReadYMLSettings()
|
|
|
|
|
@@ -62,7 +66,10 @@ func (c *CronTask) RunVcenterSnapshotHourly(ctx context.Context, logger *slog.Lo
|
|
|
|
|
for _, url := range c.Settings.Values.Settings.VcenterAddresses {
|
|
|
|
|
c.Logger.Debug("connecting to vcenter for hourly snapshot", "url", url)
|
|
|
|
|
vc := vcenter.New(c.Logger, c.VcCreds)
|
|
|
|
|
vc.Login(url)
|
|
|
|
|
if err := vc.Login(url); err != nil {
|
|
|
|
|
c.Logger.Error("unable to connect to vcenter for hourly snapshot", "error", err, "url", url)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vcVms, err := vc.GetAllVmReferences()
|
|
|
|
|
if err != nil {
|
|
|
|
|
@@ -70,6 +77,10 @@ func (c *CronTask) RunVcenterSnapshotHourly(ctx context.Context, logger *slog.Lo
|
|
|
|
|
vc.Logout()
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
canDetectMissing := len(vcVms) > 0
|
|
|
|
|
if !canDetectMissing {
|
|
|
|
|
c.Logger.Warn("no VMs returned from vcenter; skipping missing VM detection", "url", url)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inventoryRows, err := c.Database.Queries().GetInventoryByVcenter(ctx, url)
|
|
|
|
|
if err != nil {
|
|
|
|
|
@@ -116,8 +127,8 @@ func (c *CronTask) RunVcenterSnapshotHourly(ctx context.Context, logger *slog.Lo
|
|
|
|
|
presentSnapshots[vm.Reference().Value] = row
|
|
|
|
|
|
|
|
|
|
totals.VmCount++
|
|
|
|
|
totals.VcpuTotal += nullInt64ToInt(row.InitialVcpus)
|
|
|
|
|
totals.RamTotal += nullInt64ToInt(row.InitialRam)
|
|
|
|
|
totals.VcpuTotal += nullInt64ToInt(row.VcpuCount)
|
|
|
|
|
totals.RamTotal += nullInt64ToInt(row.RamGB)
|
|
|
|
|
totals.DiskTotal += nullFloat64ToFloat(row.ProvisionedDisk)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -127,7 +138,15 @@ func (c *CronTask) RunVcenterSnapshotHourly(ctx context.Context, logger *slog.Lo
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !canDetectMissing {
|
|
|
|
|
vc.Logout()
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, inv := range inventoryRows {
|
|
|
|
|
if strings.HasPrefix(inv.Name, "vCLS-") {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
vmID := inv.VmId.String
|
|
|
|
|
if vmID != "" {
|
|
|
|
|
if _, ok := presentSnapshots[vmID]; ok {
|
|
|
|
|
@@ -137,6 +156,17 @@ func (c *CronTask) RunVcenterSnapshotHourly(ctx context.Context, logger *slog.Lo
|
|
|
|
|
|
|
|
|
|
row := snapshotFromInventory(inv, startTime)
|
|
|
|
|
row.IsPresent = "FALSE"
|
|
|
|
|
if !row.DeletionTime.Valid {
|
|
|
|
|
deletionTime := startTime.Unix()
|
|
|
|
|
row.DeletionTime = sql.NullInt64{Int64: deletionTime, Valid: true}
|
|
|
|
|
if err := c.Database.Queries().InventoryMarkDeleted(ctx, queries.InventoryMarkDeletedParams{
|
|
|
|
|
DeletionTime: row.DeletionTime,
|
|
|
|
|
VmId: inv.VmId,
|
|
|
|
|
DatacenterName: inv.Datacenter,
|
|
|
|
|
}); err != nil {
|
|
|
|
|
c.Logger.Warn("failed to mark inventory record deleted", "error", err, "vm_id", row.VmId.String)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if err := insertDailyInventoryRow(ctx, dbConn, tableName, row); err != nil {
|
|
|
|
|
c.Logger.Error("failed to insert missing VM snapshot", "error", err, "vm_id", row.VmId.String)
|
|
|
|
|
}
|
|
|
|
|
@@ -148,7 +178,7 @@ func (c *CronTask) RunVcenterSnapshotHourly(ctx context.Context, logger *slog.Lo
|
|
|
|
|
"vcenter", url,
|
|
|
|
|
"vm_count", totals.VmCount,
|
|
|
|
|
"vcpu_total", totals.VcpuTotal,
|
|
|
|
|
"ram_total_mb", totals.RamTotal,
|
|
|
|
|
"ram_total_gb", totals.RamTotal,
|
|
|
|
|
"disk_total_gb", totals.DiskTotal,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
@@ -160,7 +190,7 @@ func (c *CronTask) RunVcenterSnapshotHourly(ctx context.Context, logger *slog.Lo
|
|
|
|
|
// RunVcenterDailyAggregate summarizes hourly snapshots into a daily summary table.
|
|
|
|
|
func (c *CronTask) RunVcenterDailyAggregate(ctx context.Context, logger *slog.Logger) error {
|
|
|
|
|
targetTime := time.Now().Add(-time.Minute)
|
|
|
|
|
sourceTable, err := dailyInventoryTableName(targetTime)
|
|
|
|
|
sourceTable, err := hourlyInventoryTableName(targetTime)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
@@ -173,6 +203,9 @@ func (c *CronTask) RunVcenterDailyAggregate(ctx context.Context, logger *slog.Lo
|
|
|
|
|
if err := ensureDailySummaryTable(ctx, dbConn, summaryTable); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err := report.EnsureSnapshotRegistry(ctx, c.Database); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentTotals, err := snapshotTotalsForTable(ctx, dbConn, sourceTable)
|
|
|
|
|
if err != nil {
|
|
|
|
|
@@ -182,12 +215,12 @@ func (c *CronTask) RunVcenterDailyAggregate(ctx context.Context, logger *slog.Lo
|
|
|
|
|
"table", sourceTable,
|
|
|
|
|
"vm_count", currentTotals.VmCount,
|
|
|
|
|
"vcpu_total", currentTotals.VcpuTotal,
|
|
|
|
|
"ram_total_mb", currentTotals.RamTotal,
|
|
|
|
|
"ram_total_gb", currentTotals.RamTotal,
|
|
|
|
|
"disk_total_gb", currentTotals.DiskTotal,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prevTable, _ := dailyInventoryTableName(targetTime.AddDate(0, 0, -1))
|
|
|
|
|
prevTable, _ := hourlyInventoryTableName(targetTime.AddDate(0, 0, -1))
|
|
|
|
|
if prevTable != "" && tableExists(ctx, dbConn, prevTable) {
|
|
|
|
|
prevTotals, err := snapshotTotalsForTable(ctx, dbConn, prevTable)
|
|
|
|
|
if err != nil {
|
|
|
|
|
@@ -198,7 +231,7 @@ func (c *CronTask) RunVcenterDailyAggregate(ctx context.Context, logger *slog.Lo
|
|
|
|
|
"previous_table", prevTable,
|
|
|
|
|
"vm_delta", currentTotals.VmCount-prevTotals.VmCount,
|
|
|
|
|
"vcpu_delta", currentTotals.VcpuTotal-prevTotals.VcpuTotal,
|
|
|
|
|
"ram_delta_mb", currentTotals.RamTotal-prevTotals.RamTotal,
|
|
|
|
|
"ram_delta_gb", currentTotals.RamTotal-prevTotals.RamTotal,
|
|
|
|
|
"disk_delta_gb", currentTotals.DiskTotal-prevTotals.DiskTotal,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
@@ -207,19 +240,19 @@ func (c *CronTask) RunVcenterDailyAggregate(ctx context.Context, logger *slog.Lo
|
|
|
|
|
insertQuery := fmt.Sprintf(`
|
|
|
|
|
INSERT INTO %s (
|
|
|
|
|
"InventoryId", "Name", "Vcenter", "VmId", "EventKey", "CloudId", "CreationTime", "DeletionTime",
|
|
|
|
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus",
|
|
|
|
|
"InitialRam", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid",
|
|
|
|
|
"SamplesPresent", "AvgVcpus", "AvgRam", "AvgDisk", "AvgIsPresent",
|
|
|
|
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "VcpuCount",
|
|
|
|
|
"RamGB", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid",
|
|
|
|
|
"SamplesPresent", "AvgVcpuCount", "AvgRamGB", "AvgProvisionedDisk", "AvgIsPresent",
|
|
|
|
|
"PoolTinPct", "PoolBronzePct", "PoolSilverPct", "PoolGoldPct"
|
|
|
|
|
)
|
|
|
|
|
SELECT
|
|
|
|
|
"InventoryId", "Name", "Vcenter", "VmId", "EventKey", "CloudId", "CreationTime", "DeletionTime",
|
|
|
|
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus",
|
|
|
|
|
"InitialRam", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid",
|
|
|
|
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "VcpuCount",
|
|
|
|
|
"RamGB", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid",
|
|
|
|
|
SUM(CASE WHEN "IsPresent" = 'TRUE' THEN 1 ELSE 0 END) AS "SamplesPresent",
|
|
|
|
|
AVG(CASE WHEN "IsPresent" = 'TRUE' AND "InitialVcpus" IS NOT NULL THEN "InitialVcpus" END) AS "AvgVcpus",
|
|
|
|
|
AVG(CASE WHEN "IsPresent" = 'TRUE' AND "InitialRam" IS NOT NULL THEN "InitialRam" END) AS "AvgRam",
|
|
|
|
|
AVG(CASE WHEN "IsPresent" = 'TRUE' AND "ProvisionedDisk" IS NOT NULL THEN "ProvisionedDisk" END) AS "AvgDisk",
|
|
|
|
|
AVG(CASE WHEN "IsPresent" = 'TRUE' AND "VcpuCount" IS NOT NULL THEN "VcpuCount" END) AS "AvgVcpuCount",
|
|
|
|
|
AVG(CASE WHEN "IsPresent" = 'TRUE' AND "RamGB" IS NOT NULL THEN "RamGB" END) AS "AvgRamGB",
|
|
|
|
|
AVG(CASE WHEN "IsPresent" = 'TRUE' AND "ProvisionedDisk" IS NOT NULL THEN "ProvisionedDisk" END) AS "AvgProvisionedDisk",
|
|
|
|
|
AVG(CASE WHEN "IsPresent" = 'TRUE' THEN 1 ELSE 0 END) AS "AvgIsPresent",
|
|
|
|
|
100.0 * SUM(CASE WHEN "IsPresent" = 'TRUE' AND LOWER("ResourcePool") = 'tin' THEN 1 ELSE 0 END)
|
|
|
|
|
/ NULLIF(SUM(CASE WHEN "IsPresent" = 'TRUE' THEN 1 ELSE 0 END), 0) AS "PoolTinPct",
|
|
|
|
|
@@ -232,14 +265,17 @@ SELECT
|
|
|
|
|
FROM %s
|
|
|
|
|
GROUP BY
|
|
|
|
|
"InventoryId", "Name", "Vcenter", "VmId", "EventKey", "CloudId", "CreationTime", "DeletionTime",
|
|
|
|
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus",
|
|
|
|
|
"InitialRam", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid";
|
|
|
|
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "VcpuCount",
|
|
|
|
|
"RamGB", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid";
|
|
|
|
|
`, summaryTable, sourceTable)
|
|
|
|
|
|
|
|
|
|
if _, err := dbConn.ExecContext(ctx, insertQuery); err != nil {
|
|
|
|
|
c.Logger.Error("failed to aggregate daily inventory", "error", err, "source_table", sourceTable)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err := report.RegisterSnapshot(ctx, c.Database, "daily", summaryTable, targetTime); err != nil {
|
|
|
|
|
c.Logger.Warn("failed to register daily snapshot", "error", err, "table", summaryTable)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.Logger.Debug("Finished daily inventory aggregation", "source_table", sourceTable, "summary_table", summaryTable)
|
|
|
|
|
return nil
|
|
|
|
|
@@ -251,7 +287,7 @@ func (c *CronTask) RunVcenterMonthlyAggregate(ctx context.Context, logger *slog.
|
|
|
|
|
firstOfThisMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
|
|
|
|
targetMonth := firstOfThisMonth.AddDate(0, -1, 0)
|
|
|
|
|
|
|
|
|
|
monthPrefix := fmt.Sprintf("inventory_daily_%s", targetMonth.Format("200601"))
|
|
|
|
|
monthPrefix := fmt.Sprintf("inventory_hourly_%s", targetMonth.Format("200601"))
|
|
|
|
|
dailyTables, err := report.ListTablesByPrefix(ctx, c.Database, monthPrefix)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
@@ -269,11 +305,14 @@ func (c *CronTask) RunVcenterMonthlyAggregate(ctx context.Context, logger *slog.
|
|
|
|
|
if err := ensureMonthlySummaryTable(ctx, dbConn, monthlyTable); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err := report.EnsureSnapshotRegistry(ctx, c.Database); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unionQuery := buildUnionQuery(dailyTables, []string{
|
|
|
|
|
`"InventoryId"`, `"Name"`, `"Vcenter"`, `"VmId"`, `"EventKey"`, `"CloudId"`, `"CreationTime"`,
|
|
|
|
|
`"DeletionTime"`, `"ResourcePool"`, `"VmType"`, `"Datacenter"`, `"Cluster"`, `"Folder"`,
|
|
|
|
|
`"ProvisionedDisk"`, `"InitialVcpus"`, `"InitialRam"`, `"IsTemplate"`, `"PoweredOn"`,
|
|
|
|
|
`"ProvisionedDisk"`, `"VcpuCount"`, `"RamGB"`, `"IsTemplate"`, `"PoweredOn"`,
|
|
|
|
|
`"SrmPlaceholder"`, `"VmUuid"`, `"IsPresent"`,
|
|
|
|
|
})
|
|
|
|
|
if strings.TrimSpace(unionQuery) == "" {
|
|
|
|
|
@@ -288,7 +327,7 @@ func (c *CronTask) RunVcenterMonthlyAggregate(ctx context.Context, logger *slog.
|
|
|
|
|
"month", targetMonth.Format("2006-01"),
|
|
|
|
|
"vm_count", monthlyTotals.VmCount,
|
|
|
|
|
"vcpu_total", monthlyTotals.VcpuTotal,
|
|
|
|
|
"ram_total_mb", monthlyTotals.RamTotal,
|
|
|
|
|
"ram_total_gb", monthlyTotals.RamTotal,
|
|
|
|
|
"disk_total_gb", monthlyTotals.DiskTotal,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
@@ -296,18 +335,18 @@ func (c *CronTask) RunVcenterMonthlyAggregate(ctx context.Context, logger *slog.
|
|
|
|
|
insertQuery := fmt.Sprintf(`
|
|
|
|
|
INSERT INTO %s (
|
|
|
|
|
"InventoryId", "Name", "Vcenter", "VmId", "EventKey", "CloudId", "CreationTime", "DeletionTime",
|
|
|
|
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus",
|
|
|
|
|
"InitialRam", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid",
|
|
|
|
|
"AvgVcpus", "AvgRam", "AvgDisk", "AvgIsPresent",
|
|
|
|
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "VcpuCount",
|
|
|
|
|
"RamGB", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid",
|
|
|
|
|
"AvgVcpuCount", "AvgRamGB", "AvgProvisionedDisk", "AvgIsPresent",
|
|
|
|
|
"PoolTinPct", "PoolBronzePct", "PoolSilverPct", "PoolGoldPct"
|
|
|
|
|
)
|
|
|
|
|
SELECT
|
|
|
|
|
"InventoryId", "Name", "Vcenter", "VmId", "EventKey", "CloudId", "CreationTime", "DeletionTime",
|
|
|
|
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus",
|
|
|
|
|
"InitialRam", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid",
|
|
|
|
|
AVG(CASE WHEN "InitialVcpus" IS NOT NULL THEN "InitialVcpus" END) AS "AvgVcpus",
|
|
|
|
|
AVG(CASE WHEN "InitialRam" IS NOT NULL THEN "InitialRam" END) AS "AvgRam",
|
|
|
|
|
AVG(CASE WHEN "ProvisionedDisk" IS NOT NULL THEN "ProvisionedDisk" END) AS "AvgDisk",
|
|
|
|
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "VcpuCount",
|
|
|
|
|
"RamGB", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid",
|
|
|
|
|
AVG(CASE WHEN "VcpuCount" IS NOT NULL THEN "VcpuCount" END) AS "AvgVcpuCount",
|
|
|
|
|
AVG(CASE WHEN "RamGB" IS NOT NULL THEN "RamGB" END) AS "AvgRamGB",
|
|
|
|
|
AVG(CASE WHEN "ProvisionedDisk" IS NOT NULL THEN "ProvisionedDisk" END) AS "AvgProvisionedDisk",
|
|
|
|
|
AVG(CASE WHEN "IsPresent" = 'TRUE' THEN 1 ELSE 0 END) AS "AvgIsPresent",
|
|
|
|
|
100.0 * SUM(CASE WHEN "IsPresent" = 'TRUE' AND LOWER("ResourcePool") = 'tin' THEN 1 ELSE 0 END)
|
|
|
|
|
/ NULLIF(SUM(CASE WHEN "IsPresent" = 'TRUE' THEN 1 ELSE 0 END), 0) AS "PoolTinPct",
|
|
|
|
|
@@ -322,14 +361,17 @@ FROM (
|
|
|
|
|
) snapshots
|
|
|
|
|
GROUP BY
|
|
|
|
|
"InventoryId", "Name", "Vcenter", "VmId", "EventKey", "CloudId", "CreationTime", "DeletionTime",
|
|
|
|
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus",
|
|
|
|
|
"InitialRam", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid";
|
|
|
|
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "VcpuCount",
|
|
|
|
|
"RamGB", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid";
|
|
|
|
|
`, monthlyTable, unionQuery)
|
|
|
|
|
|
|
|
|
|
if _, err := dbConn.ExecContext(ctx, insertQuery); err != nil {
|
|
|
|
|
c.Logger.Error("failed to aggregate monthly inventory", "error", err, "month", targetMonth.Format("2006-01"))
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err := report.RegisterSnapshot(ctx, c.Database, "monthly", monthlyTable, targetMonth); err != nil {
|
|
|
|
|
c.Logger.Warn("failed to register monthly snapshot", "error", err, "table", monthlyTable)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.Logger.Debug("Finished monthly inventory aggregation", "summary_table", monthlyTable)
|
|
|
|
|
return nil
|
|
|
|
|
@@ -338,15 +380,15 @@ GROUP BY
|
|
|
|
|
// RunSnapshotCleanup drops hourly and daily snapshot tables older than retention.
|
|
|
|
|
func (c *CronTask) RunSnapshotCleanup(ctx context.Context, logger *slog.Logger) error {
|
|
|
|
|
now := time.Now()
|
|
|
|
|
hourlyMaxDays := getEnvInt("HOURLY_SNAPSHOT_MAX_AGE_DAYS", 60)
|
|
|
|
|
dailyMaxMonths := getEnvInt("DAILY_SNAPSHOT_MAX_AGE_MONTHS", 12)
|
|
|
|
|
hourlyMaxDays := intWithDefault(c.Settings.Values.Settings.HourlySnapshotMaxAgeDays, 60)
|
|
|
|
|
dailyMaxMonths := intWithDefault(c.Settings.Values.Settings.DailySnapshotMaxAgeMonths, 12)
|
|
|
|
|
|
|
|
|
|
hourlyCutoff := now.AddDate(0, 0, -hourlyMaxDays)
|
|
|
|
|
dailyCutoff := now.AddDate(0, -dailyMaxMonths, 0)
|
|
|
|
|
|
|
|
|
|
dbConn := c.Database.DB()
|
|
|
|
|
|
|
|
|
|
hourlyTables, err := report.ListTablesByPrefix(ctx, c.Database, "inventory_daily_")
|
|
|
|
|
hourlyTables, err := report.ListTablesByPrefix(ctx, c.Database, "inventory_hourly_")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
@@ -356,7 +398,7 @@ func (c *CronTask) RunSnapshotCleanup(ctx context.Context, logger *slog.Logger)
|
|
|
|
|
if strings.HasPrefix(table, "inventory_daily_summary_") {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
tableDate, ok := parseSnapshotDate(table, "inventory_daily_", "20060102")
|
|
|
|
|
tableDate, ok := parseSnapshotDate(table, "inventory_hourly_", "2006010215")
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
@@ -365,6 +407,9 @@ func (c *CronTask) RunSnapshotCleanup(ctx context.Context, logger *slog.Logger)
|
|
|
|
|
c.Logger.Error("failed to drop hourly snapshot table", "error", err, "table", table)
|
|
|
|
|
} else {
|
|
|
|
|
removedHourly++
|
|
|
|
|
if err := report.DeleteSnapshotRecord(ctx, c.Database, table); err != nil {
|
|
|
|
|
c.Logger.Warn("failed to remove hourly snapshot registry entry", "error", err, "table", table)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -384,6 +429,9 @@ func (c *CronTask) RunSnapshotCleanup(ctx context.Context, logger *slog.Logger)
|
|
|
|
|
c.Logger.Error("failed to drop daily snapshot table", "error", err, "table", table)
|
|
|
|
|
} else {
|
|
|
|
|
removedDaily++
|
|
|
|
|
if err := report.DeleteSnapshotRecord(ctx, c.Database, table); err != nil {
|
|
|
|
|
c.Logger.Warn("failed to remove daily snapshot registry entry", "error", err, "table", table)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -397,8 +445,8 @@ func (c *CronTask) RunSnapshotCleanup(ctx context.Context, logger *slog.Logger)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func dailyInventoryTableName(t time.Time) (string, error) {
|
|
|
|
|
return safeTableName(fmt.Sprintf("inventory_daily_%s", t.Format("20060102")))
|
|
|
|
|
func hourlyInventoryTableName(t time.Time) (string, error) {
|
|
|
|
|
return safeTableName(fmt.Sprintf("inventory_hourly_%s", t.Format("2006010215")))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func dailySummaryTableName(t time.Time) (string, error) {
|
|
|
|
|
@@ -435,8 +483,8 @@ func ensureDailyInventoryTable(ctx context.Context, dbConn *sqlx.DB, tableName s
|
|
|
|
|
"Cluster" TEXT,
|
|
|
|
|
"Folder" TEXT,
|
|
|
|
|
"ProvisionedDisk" REAL,
|
|
|
|
|
"InitialVcpus" BIGINT,
|
|
|
|
|
"InitialRam" BIGINT,
|
|
|
|
|
"VcpuCount" BIGINT,
|
|
|
|
|
"RamGB" BIGINT,
|
|
|
|
|
"IsTemplate" TEXT,
|
|
|
|
|
"PoweredOn" TEXT,
|
|
|
|
|
"SrmPlaceholder" TEXT,
|
|
|
|
|
@@ -445,8 +493,14 @@ func ensureDailyInventoryTable(ctx context.Context, dbConn *sqlx.DB, tableName s
|
|
|
|
|
"IsPresent" TEXT NOT NULL
|
|
|
|
|
);`, tableName)
|
|
|
|
|
|
|
|
|
|
_, err := dbConn.ExecContext(ctx, ddl)
|
|
|
|
|
return err
|
|
|
|
|
if _, err := dbConn.ExecContext(ctx, ddl); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ensureSnapshotColumns(ctx, dbConn, tableName, []columnDef{
|
|
|
|
|
{Name: "VcpuCount", Type: "BIGINT"},
|
|
|
|
|
{Name: "RamGB", Type: "BIGINT"},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ensureDailySummaryTable(ctx context.Context, dbConn *sqlx.DB, tableName string) error {
|
|
|
|
|
@@ -465,16 +519,16 @@ func ensureDailySummaryTable(ctx context.Context, dbConn *sqlx.DB, tableName str
|
|
|
|
|
"Cluster" TEXT,
|
|
|
|
|
"Folder" TEXT,
|
|
|
|
|
"ProvisionedDisk" REAL,
|
|
|
|
|
"InitialVcpus" BIGINT,
|
|
|
|
|
"InitialRam" BIGINT,
|
|
|
|
|
"VcpuCount" BIGINT,
|
|
|
|
|
"RamGB" BIGINT,
|
|
|
|
|
"IsTemplate" TEXT,
|
|
|
|
|
"PoweredOn" TEXT,
|
|
|
|
|
"SrmPlaceholder" TEXT,
|
|
|
|
|
"VmUuid" TEXT,
|
|
|
|
|
"SamplesPresent" BIGINT NOT NULL,
|
|
|
|
|
"AvgVcpus" REAL,
|
|
|
|
|
"AvgRam" REAL,
|
|
|
|
|
"AvgDisk" REAL,
|
|
|
|
|
"AvgVcpuCount" REAL,
|
|
|
|
|
"AvgRamGB" REAL,
|
|
|
|
|
"AvgProvisionedDisk" REAL,
|
|
|
|
|
"AvgIsPresent" REAL,
|
|
|
|
|
"PoolTinPct" REAL,
|
|
|
|
|
"PoolBronzePct" REAL,
|
|
|
|
|
@@ -487,9 +541,9 @@ func ensureDailySummaryTable(ctx context.Context, dbConn *sqlx.DB, tableName str
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ensureSnapshotColumns(ctx, dbConn, tableName, []columnDef{
|
|
|
|
|
{Name: "AvgVcpus", Type: "REAL"},
|
|
|
|
|
{Name: "AvgRam", Type: "REAL"},
|
|
|
|
|
{Name: "AvgDisk", Type: "REAL"},
|
|
|
|
|
{Name: "AvgVcpuCount", Type: "REAL"},
|
|
|
|
|
{Name: "AvgRamGB", Type: "REAL"},
|
|
|
|
|
{Name: "AvgProvisionedDisk", Type: "REAL"},
|
|
|
|
|
{Name: "AvgIsPresent", Type: "REAL"},
|
|
|
|
|
{Name: "PoolTinPct", Type: "REAL"},
|
|
|
|
|
{Name: "PoolBronzePct", Type: "REAL"},
|
|
|
|
|
@@ -514,15 +568,15 @@ func ensureMonthlySummaryTable(ctx context.Context, dbConn *sqlx.DB, tableName s
|
|
|
|
|
"Cluster" TEXT,
|
|
|
|
|
"Folder" TEXT,
|
|
|
|
|
"ProvisionedDisk" REAL,
|
|
|
|
|
"InitialVcpus" BIGINT,
|
|
|
|
|
"InitialRam" BIGINT,
|
|
|
|
|
"VcpuCount" BIGINT,
|
|
|
|
|
"RamGB" BIGINT,
|
|
|
|
|
"IsTemplate" TEXT,
|
|
|
|
|
"PoweredOn" TEXT,
|
|
|
|
|
"SrmPlaceholder" TEXT,
|
|
|
|
|
"VmUuid" TEXT,
|
|
|
|
|
"AvgVcpus" REAL,
|
|
|
|
|
"AvgRam" REAL,
|
|
|
|
|
"AvgDisk" REAL,
|
|
|
|
|
"AvgVcpuCount" REAL,
|
|
|
|
|
"AvgRamGB" REAL,
|
|
|
|
|
"AvgProvisionedDisk" REAL,
|
|
|
|
|
"AvgIsPresent" REAL,
|
|
|
|
|
"PoolTinPct" REAL,
|
|
|
|
|
"PoolBronzePct" REAL,
|
|
|
|
|
@@ -535,7 +589,10 @@ func ensureMonthlySummaryTable(ctx context.Context, dbConn *sqlx.DB, tableName s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ensureSnapshotColumns(ctx, dbConn, tableName, []columnDef{
|
|
|
|
|
{Name: "AvgDisk", Type: "REAL"},
|
|
|
|
|
{Name: "AvgVcpuCount", Type: "REAL"},
|
|
|
|
|
{Name: "AvgRamGB", Type: "REAL"},
|
|
|
|
|
{Name: "AvgProvisionedDisk", Type: "REAL"},
|
|
|
|
|
{Name: "AvgIsPresent", Type: "REAL"},
|
|
|
|
|
{Name: "PoolTinPct", Type: "REAL"},
|
|
|
|
|
{Name: "PoolBronzePct", Type: "REAL"},
|
|
|
|
|
{Name: "PoolSilverPct", Type: "REAL"},
|
|
|
|
|
@@ -622,8 +679,8 @@ func snapshotTotalsForTable(ctx context.Context, dbConn *sqlx.DB, table string)
|
|
|
|
|
query := fmt.Sprintf(`
|
|
|
|
|
SELECT
|
|
|
|
|
COUNT(DISTINCT "VmId") AS vm_count,
|
|
|
|
|
COALESCE(SUM(CASE WHEN "InitialVcpus" IS NOT NULL THEN "InitialVcpus" ELSE 0 END), 0) AS vcpu_total,
|
|
|
|
|
COALESCE(SUM(CASE WHEN "InitialRam" IS NOT NULL THEN "InitialRam" ELSE 0 END), 0) AS ram_total,
|
|
|
|
|
COALESCE(SUM(CASE WHEN "VcpuCount" IS NOT NULL THEN "VcpuCount" ELSE 0 END), 0) AS vcpu_total,
|
|
|
|
|
COALESCE(SUM(CASE WHEN "RamGB" IS NOT NULL THEN "RamGB" ELSE 0 END), 0) AS ram_total,
|
|
|
|
|
COALESCE(SUM(CASE WHEN "ProvisionedDisk" IS NOT NULL THEN "ProvisionedDisk" ELSE 0 END), 0) AS disk_total
|
|
|
|
|
FROM %s
|
|
|
|
|
WHERE "IsPresent" = 'TRUE'
|
|
|
|
|
@@ -640,8 +697,8 @@ func snapshotTotalsForUnion(ctx context.Context, dbConn *sqlx.DB, unionQuery str
|
|
|
|
|
query := fmt.Sprintf(`
|
|
|
|
|
SELECT
|
|
|
|
|
COUNT(DISTINCT "VmId") AS vm_count,
|
|
|
|
|
COALESCE(SUM(CASE WHEN "InitialVcpus" IS NOT NULL THEN "InitialVcpus" ELSE 0 END), 0) AS vcpu_total,
|
|
|
|
|
COALESCE(SUM(CASE WHEN "InitialRam" IS NOT NULL THEN "InitialRam" ELSE 0 END), 0) AS ram_total,
|
|
|
|
|
COALESCE(SUM(CASE WHEN "VcpuCount" IS NOT NULL THEN "VcpuCount" ELSE 0 END), 0) AS vcpu_total,
|
|
|
|
|
COALESCE(SUM(CASE WHEN "RamGB" IS NOT NULL THEN "RamGB" ELSE 0 END), 0) AS ram_total,
|
|
|
|
|
COALESCE(SUM(CASE WHEN "ProvisionedDisk" IS NOT NULL THEN "ProvisionedDisk" ELSE 0 END), 0) AS disk_total
|
|
|
|
|
FROM (
|
|
|
|
|
%s
|
|
|
|
|
@@ -694,13 +751,8 @@ func nullFloat64ToFloat(value sql.NullFloat64) float64 {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getEnvInt(key string, fallback int) int {
|
|
|
|
|
raw := strings.TrimSpace(os.Getenv(key))
|
|
|
|
|
if raw == "" {
|
|
|
|
|
return fallback
|
|
|
|
|
}
|
|
|
|
|
value, err := strconv.Atoi(raw)
|
|
|
|
|
if err != nil || value < 0 {
|
|
|
|
|
func intWithDefault(value int, fallback int) int {
|
|
|
|
|
if value <= 0 {
|
|
|
|
|
return fallback
|
|
|
|
|
}
|
|
|
|
|
return value
|
|
|
|
|
@@ -731,8 +783,8 @@ func snapshotFromVM(vmObject *mo.VirtualMachine, vc *vcenter.Vcenter, snapshotTi
|
|
|
|
|
if !vmObject.Config.CreateDate.IsZero() {
|
|
|
|
|
row.CreationTime = sql.NullInt64{Int64: vmObject.Config.CreateDate.Unix(), Valid: true}
|
|
|
|
|
}
|
|
|
|
|
row.InitialVcpus = sql.NullInt64{Int64: int64(vmObject.Config.Hardware.NumCPU), Valid: vmObject.Config.Hardware.NumCPU > 0}
|
|
|
|
|
row.InitialRam = sql.NullInt64{Int64: int64(vmObject.Config.Hardware.MemoryMB), Valid: vmObject.Config.Hardware.MemoryMB > 0}
|
|
|
|
|
row.VcpuCount = sql.NullInt64{Int64: int64(vmObject.Config.Hardware.NumCPU), Valid: vmObject.Config.Hardware.NumCPU > 0}
|
|
|
|
|
row.RamGB = sql.NullInt64{Int64: int64(vmObject.Config.Hardware.MemoryMB) / 1024, Valid: vmObject.Config.Hardware.MemoryMB > 0}
|
|
|
|
|
|
|
|
|
|
totalDiskBytes := int64(0)
|
|
|
|
|
for _, device := range vmObject.Config.Hardware.Device {
|
|
|
|
|
@@ -774,11 +826,11 @@ func snapshotFromVM(vmObject *mo.VirtualMachine, vc *vcenter.Vcenter, snapshotTi
|
|
|
|
|
if !row.ProvisionedDisk.Valid {
|
|
|
|
|
row.ProvisionedDisk = inv.ProvisionedDisk
|
|
|
|
|
}
|
|
|
|
|
if !row.InitialVcpus.Valid {
|
|
|
|
|
row.InitialVcpus = inv.InitialVcpus
|
|
|
|
|
if !row.VcpuCount.Valid {
|
|
|
|
|
row.VcpuCount = inv.InitialVcpus
|
|
|
|
|
}
|
|
|
|
|
if !row.InitialRam.Valid {
|
|
|
|
|
row.InitialRam = inv.InitialRam
|
|
|
|
|
if !row.RamGB.Valid && inv.InitialRam.Valid {
|
|
|
|
|
row.RamGB = sql.NullInt64{Int64: inv.InitialRam.Int64 / 1024, Valid: inv.InitialRam.Int64 > 0}
|
|
|
|
|
}
|
|
|
|
|
if row.IsTemplate == "" {
|
|
|
|
|
row.IsTemplate = boolStringFromInterface(inv.IsTemplate)
|
|
|
|
|
@@ -837,8 +889,8 @@ func snapshotFromInventory(inv queries.Inventory, snapshotTime time.Time) invent
|
|
|
|
|
Cluster: inv.Cluster,
|
|
|
|
|
Folder: inv.Folder,
|
|
|
|
|
ProvisionedDisk: inv.ProvisionedDisk,
|
|
|
|
|
InitialVcpus: inv.InitialVcpus,
|
|
|
|
|
InitialRam: inv.InitialRam,
|
|
|
|
|
VcpuCount: inv.InitialVcpus,
|
|
|
|
|
RamGB: sql.NullInt64{Int64: inv.InitialRam.Int64 / 1024, Valid: inv.InitialRam.Valid && inv.InitialRam.Int64 > 0},
|
|
|
|
|
IsTemplate: boolStringFromInterface(inv.IsTemplate),
|
|
|
|
|
PoweredOn: boolStringFromInterface(inv.PoweredOn),
|
|
|
|
|
SrmPlaceholder: boolStringFromInterface(inv.SrmPlaceholder),
|
|
|
|
|
@@ -851,8 +903,8 @@ func insertDailyInventoryRow(ctx context.Context, dbConn *sqlx.DB, tableName str
|
|
|
|
|
query := fmt.Sprintf(`
|
|
|
|
|
INSERT INTO %s (
|
|
|
|
|
"InventoryId", "Name", "Vcenter", "VmId", "EventKey", "CloudId", "CreationTime", "DeletionTime",
|
|
|
|
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus",
|
|
|
|
|
"InitialRam", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid", "SnapshotTime", "IsPresent"
|
|
|
|
|
"ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "VcpuCount",
|
|
|
|
|
"RamGB", "IsTemplate", "PoweredOn", "SrmPlaceholder", "VmUuid", "SnapshotTime", "IsPresent"
|
|
|
|
|
)
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
|
|
|
|
`, tableName)
|
|
|
|
|
@@ -874,8 +926,8 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
|
|
|
|
row.Cluster,
|
|
|
|
|
row.Folder,
|
|
|
|
|
row.ProvisionedDisk,
|
|
|
|
|
row.InitialVcpus,
|
|
|
|
|
row.InitialRam,
|
|
|
|
|
row.VcpuCount,
|
|
|
|
|
row.RamGB,
|
|
|
|
|
row.IsTemplate,
|
|
|
|
|
row.PoweredOn,
|
|
|
|
|
row.SrmPlaceholder,
|
|
|
|
|
|