Add vCenter cache rebuild functionality and related API endpoint
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:
@@ -749,6 +749,12 @@ func CreateTableReport(logger *slog.Logger, Database db.Database, ctx context.Co
|
||||
}
|
||||
|
||||
if isDailySummary || isMonthlySummary {
|
||||
reportHeaders := make([]string, 0, len(specs))
|
||||
for _, spec := range specs {
|
||||
reportHeaders = append(reportHeaders, spec.Name)
|
||||
}
|
||||
addSummaryPivotSheet(logger, xlsx, sheetName, reportHeaders, rowCount, tableName)
|
||||
|
||||
meta := reportMetadata{
|
||||
TableName: tableName,
|
||||
ReportType: reportTypeFromTable(tableName),
|
||||
@@ -883,6 +889,144 @@ func addTotalsChartSheet(logger *slog.Logger, database db.Database, ctx context.
|
||||
}
|
||||
}
|
||||
|
||||
type summaryPivotSpec struct {
|
||||
Title string
|
||||
TitleCell string
|
||||
PivotName string
|
||||
PivotRange string
|
||||
RowFields []string
|
||||
DataField string
|
||||
DataName string
|
||||
DataSummary string
|
||||
}
|
||||
|
||||
func addSummaryPivotSheet(logger *slog.Logger, xlsx *excelize.File, dataSheet string, headers []string, rowCount int, tableName string) {
|
||||
if logger == nil {
|
||||
logger = slog.Default()
|
||||
}
|
||||
const summarySheet = "Summary"
|
||||
if _, err := xlsx.NewSheet(summarySheet); err != nil {
|
||||
logger.Warn("failed to create summary worksheet", "table", tableName, "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
if rowCount <= 0 {
|
||||
xlsx.SetCellValue(summarySheet, "A1", "Summary")
|
||||
xlsx.SetCellValue(summarySheet, "A3", "No data rows were available to build pivot tables.")
|
||||
if err := SetColAutoWidth(xlsx, summarySheet); err != nil {
|
||||
logger.Warn("failed to size summary worksheet columns", "table", tableName, "error", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(headers) == 0 {
|
||||
logger.Warn("summary worksheet skipped due to empty headers", "table", tableName)
|
||||
return
|
||||
}
|
||||
|
||||
endCell, err := excelize.CoordinatesToCellName(len(headers), rowCount+1)
|
||||
if err != nil {
|
||||
logger.Warn("summary worksheet skipped due to invalid data range", "table", tableName, "error", err)
|
||||
return
|
||||
}
|
||||
dataRange := fmt.Sprintf("%s!A1:%s", quoteSheetName(dataSheet), endCell)
|
||||
lowerToHeader := make(map[string]string, len(headers))
|
||||
for _, header := range headers {
|
||||
lowerToHeader[strings.ToLower(strings.TrimSpace(header))] = header
|
||||
}
|
||||
resolveField := func(name string) (string, bool) {
|
||||
header, ok := lowerToHeader[strings.ToLower(strings.TrimSpace(name))]
|
||||
return header, ok
|
||||
}
|
||||
|
||||
specs := []summaryPivotSpec{
|
||||
{
|
||||
Title: "Sum of Avg vCPUs",
|
||||
TitleCell: "A1",
|
||||
PivotName: "PivotAvgVcpu",
|
||||
PivotRange: fmt.Sprintf("%s!A3:H1000", quoteSheetName(summarySheet)),
|
||||
RowFields: []string{"Datacenter", "ResourcePool"},
|
||||
DataField: "AvgVcpuCount",
|
||||
DataName: "Sum of Avg vCPUs",
|
||||
DataSummary: "Sum",
|
||||
},
|
||||
{
|
||||
Title: "Sum of Avg RAM",
|
||||
TitleCell: "J1",
|
||||
PivotName: "PivotAvgRam",
|
||||
PivotRange: fmt.Sprintf("%s!J3:P1000", quoteSheetName(summarySheet)),
|
||||
RowFields: []string{"Datacenter"},
|
||||
DataField: "AvgRamGB",
|
||||
DataName: "Sum of Avg RAM",
|
||||
DataSummary: "Sum",
|
||||
},
|
||||
{
|
||||
Title: "Sum of prorated VM count",
|
||||
TitleCell: "A1003",
|
||||
PivotName: "PivotProratedVmCount",
|
||||
PivotRange: fmt.Sprintf("%s!A1005:H2002", quoteSheetName(summarySheet)),
|
||||
RowFields: []string{"Datacenter"},
|
||||
DataField: "AvgIsPresent",
|
||||
DataName: "Sum of prorated VM count",
|
||||
DataSummary: "Sum",
|
||||
},
|
||||
{
|
||||
Title: "Count of VM Name",
|
||||
TitleCell: "J1003",
|
||||
PivotName: "PivotVmNameCount",
|
||||
PivotRange: fmt.Sprintf("%s!J1005:P2002", quoteSheetName(summarySheet)),
|
||||
RowFields: []string{"Datacenter"},
|
||||
DataField: "Name",
|
||||
DataName: "Count of VM Name",
|
||||
DataSummary: "Count",
|
||||
},
|
||||
}
|
||||
|
||||
for _, spec := range specs {
|
||||
xlsx.SetCellValue(summarySheet, spec.TitleCell, spec.Title)
|
||||
|
||||
rows := make([]excelize.PivotTableField, 0, len(spec.RowFields))
|
||||
missingField := false
|
||||
for _, rowField := range spec.RowFields {
|
||||
resolved, ok := resolveField(rowField)
|
||||
if !ok {
|
||||
logger.Warn("summary pivot skipped: missing row field", "table", tableName, "pivot", spec.PivotName, "field", rowField)
|
||||
missingField = true
|
||||
break
|
||||
}
|
||||
rows = append(rows, excelize.PivotTableField{Data: resolved})
|
||||
}
|
||||
if missingField {
|
||||
continue
|
||||
}
|
||||
dataField, ok := resolveField(spec.DataField)
|
||||
if !ok {
|
||||
logger.Warn("summary pivot skipped: missing data field", "table", tableName, "pivot", spec.PivotName, "field", spec.DataField)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := xlsx.AddPivotTable(&excelize.PivotTableOptions{
|
||||
Name: spec.PivotName,
|
||||
DataRange: dataRange,
|
||||
PivotTableRange: spec.PivotRange,
|
||||
Rows: rows,
|
||||
Data: []excelize.PivotTableField{{Data: dataField, Name: spec.DataName, Subtotal: spec.DataSummary}},
|
||||
RowGrandTotals: true,
|
||||
ColGrandTotals: true,
|
||||
ShowDrill: true,
|
||||
ShowRowHeaders: true,
|
||||
ShowColHeaders: true,
|
||||
ShowLastColumn: true,
|
||||
PivotTableStyleName: "PivotStyleLight16",
|
||||
}); err != nil {
|
||||
logger.Warn("failed to add summary pivot table", "table", tableName, "pivot", spec.PivotName, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := SetColAutoWidth(xlsx, summarySheet); err != nil {
|
||||
logger.Warn("failed to size summary worksheet columns", "table", tableName, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func tableColumns(ctx context.Context, dbConn *sqlx.DB, tableName string) ([]string, error) {
|
||||
driver := strings.ToLower(dbConn.DriverName())
|
||||
switch driver {
|
||||
@@ -1464,29 +1608,29 @@ FROM diag, agg_diag
|
||||
`, vmKeyExpr, overlapExpr, selected.TableName, templateExclusionFilter(), vmKeyExpr, prevTableName, templateExclusionFilter(), missingOverlapExpr, aggOverlapExpr)
|
||||
query = dbConn.Rebind(query)
|
||||
var row struct {
|
||||
VmCount int64 `db:"vm_count"`
|
||||
VcpuTotal int64 `db:"vcpu_total"`
|
||||
RamTotal int64 `db:"ram_total"`
|
||||
PresenceRatio float64 `db:"presence_ratio"`
|
||||
TinTotal float64 `db:"tin_total"`
|
||||
BronzeTotal float64 `db:"bronze_total"`
|
||||
SilverTotal float64 `db:"silver_total"`
|
||||
GoldTotal float64 `db:"gold_total"`
|
||||
RowCount int64 `db:"row_count"`
|
||||
DistinctKeys int64 `db:"distinct_keys"`
|
||||
UnknownKeys int64 `db:"unknown_keys"`
|
||||
MissingVmID int64 `db:"missing_vm_id"`
|
||||
MissingVmUUID int64 `db:"missing_vm_uuid"`
|
||||
MissingName int64 `db:"missing_name"`
|
||||
PresenceOverOne int64 `db:"presence_over_one"`
|
||||
PresenceUnderZero int64 `db:"presence_under_zero"`
|
||||
BasePresenceSum float64 `db:"base_presence_sum"`
|
||||
AggCount int64 `db:"agg_count"`
|
||||
MissingCreation int64 `db:"missing_creation"`
|
||||
MissingDeletion int64 `db:"missing_deletion"`
|
||||
CreatedInInterval int64 `db:"created_in_interval"`
|
||||
DeletedInInterval int64 `db:"deleted_in_interval"`
|
||||
PartialPresence int64 `db:"partial_presence"`
|
||||
VmCount int64 `db:"vm_count"`
|
||||
VcpuTotal int64 `db:"vcpu_total"`
|
||||
RamTotal int64 `db:"ram_total"`
|
||||
PresenceRatio float64 `db:"presence_ratio"`
|
||||
TinTotal float64 `db:"tin_total"`
|
||||
BronzeTotal float64 `db:"bronze_total"`
|
||||
SilverTotal float64 `db:"silver_total"`
|
||||
GoldTotal float64 `db:"gold_total"`
|
||||
RowCount int64 `db:"row_count"`
|
||||
DistinctKeys int64 `db:"distinct_keys"`
|
||||
UnknownKeys int64 `db:"unknown_keys"`
|
||||
MissingVmID int64 `db:"missing_vm_id"`
|
||||
MissingVmUUID int64 `db:"missing_vm_uuid"`
|
||||
MissingName int64 `db:"missing_name"`
|
||||
PresenceOverOne int64 `db:"presence_over_one"`
|
||||
PresenceUnderZero int64 `db:"presence_under_zero"`
|
||||
BasePresenceSum float64 `db:"base_presence_sum"`
|
||||
AggCount int64 `db:"agg_count"`
|
||||
MissingCreation int64 `db:"missing_creation"`
|
||||
MissingDeletion int64 `db:"missing_deletion"`
|
||||
CreatedInInterval int64 `db:"created_in_interval"`
|
||||
DeletedInInterval int64 `db:"deleted_in_interval"`
|
||||
PartialPresence int64 `db:"partial_presence"`
|
||||
}
|
||||
overlapArgs := []interface{}{
|
||||
hourEndUnix, hourEndUnix,
|
||||
|
||||
@@ -874,7 +874,7 @@ func (v *Vcenter) BuildResourcePoolLookup() (map[string]string, error) {
|
||||
|
||||
// Helper function to retrieve the full folder path for the VM
|
||||
func (v *Vcenter) GetVMFolderPath(vm mo.VirtualMachine) (string, error) {
|
||||
v.Logger.Debug("Commencing vm folder path search")
|
||||
v.Logger.Debug("commencing vm folder path search", "vcenter", v.Vurl, "vm_id", vm.Reference().Value)
|
||||
|
||||
entities, err := mo.Ancestors(v.ctx, v.client.Client, v.client.ServiceContent.PropertyCollector, vm.Reference())
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user