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:
162
server/handler/vcenterCacheRebuild.go
Normal file
162
server/handler/vcenterCacheRebuild.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"vctp/db"
|
||||
"vctp/internal/vcenter"
|
||||
"vctp/server/models"
|
||||
)
|
||||
|
||||
// VcenterCacheRebuild force-regenerates cached vCenter reference data in the database.
|
||||
// @Summary Rebuild vCenter object cache
|
||||
// @Description Rebuilds cached folder/resource-pool/host(cluster+datacenter) references from vCenter and rewrites the database cache tables.
|
||||
// @Tags vcenters
|
||||
// @Produce json
|
||||
// @Param vcenter query string false "Optional single vCenter URL to rebuild; defaults to all configured vCenters"
|
||||
// @Success 200 {object} models.VcenterCacheRebuildResponse "Cache rebuild summary"
|
||||
// @Failure 400 {object} models.ErrorResponse "Invalid request"
|
||||
// @Failure 405 {object} models.ErrorResponse "Method not allowed"
|
||||
// @Failure 500 {object} models.VcenterCacheRebuildResponse "All rebuild attempts failed"
|
||||
// @Router /api/vcenters/cache/rebuild [post]
|
||||
func (h *Handler) VcenterCacheRebuild(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
writeJSONError(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
h.Settings.ReadYMLSettings()
|
||||
|
||||
requested := strings.TrimSpace(r.URL.Query().Get("vcenter"))
|
||||
configured := h.Settings.Values.Settings.VcenterAddresses
|
||||
targets := make([]string, 0, len(configured))
|
||||
for _, raw := range configured {
|
||||
vcURL := strings.TrimSpace(raw)
|
||||
if vcURL == "" {
|
||||
continue
|
||||
}
|
||||
if requested != "" && vcURL != requested {
|
||||
continue
|
||||
}
|
||||
targets = append(targets, vcURL)
|
||||
}
|
||||
|
||||
if requested != "" && len(targets) == 0 {
|
||||
writeJSONError(w, http.StatusBadRequest, fmt.Sprintf("requested vcenter is not configured: %s", requested))
|
||||
return
|
||||
}
|
||||
if len(targets) == 0 {
|
||||
writeJSONError(w, http.StatusBadRequest, "no vcenter addresses configured")
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.EnsureVcenterReferenceCacheTables(ctx, h.Database.DB()); err != nil {
|
||||
h.Logger.Error("failed to ensure vcenter cache tables", "error", err)
|
||||
writeJSONError(w, http.StatusInternalServerError, "failed to ensure vcenter cache tables")
|
||||
return
|
||||
}
|
||||
|
||||
resp := models.VcenterCacheRebuildResponse{
|
||||
Status: "OK",
|
||||
Total: len(targets),
|
||||
Results: make([]models.VcenterCacheRebuildResult, 0, len(targets)),
|
||||
}
|
||||
|
||||
for _, vcURL := range targets {
|
||||
start := time.Now()
|
||||
result := models.VcenterCacheRebuildResult{
|
||||
Vcenter: vcURL,
|
||||
}
|
||||
|
||||
folderEntries, poolEntries, hostEntries, err := h.rebuildOneVcenterCache(ctx, vcURL)
|
||||
result.DurationSeconds = math.Round(time.Since(start).Seconds()*1000) / 1000
|
||||
if err != nil {
|
||||
result.Error = err.Error()
|
||||
resp.Failed++
|
||||
h.Logger.Warn("vcenter cache rebuild failed", "vcenter", vcURL, "error", err)
|
||||
} else {
|
||||
result.FolderEntries = folderEntries
|
||||
result.ResourcePoolEntries = poolEntries
|
||||
result.HostEntries = hostEntries
|
||||
resp.Succeeded++
|
||||
h.Logger.Info("vcenter cache rebuild completed", "vcenter", vcURL, "folder_entries", folderEntries, "resource_pool_entries", poolEntries, "host_entries", hostEntries, "duration", time.Since(start))
|
||||
}
|
||||
resp.Results = append(resp.Results, result)
|
||||
}
|
||||
|
||||
switch {
|
||||
case resp.Failed == 0:
|
||||
resp.Status = "OK"
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
case resp.Succeeded == 0:
|
||||
resp.Status = "ERROR"
|
||||
writeJSON(w, http.StatusInternalServerError, resp)
|
||||
default:
|
||||
resp.Status = "PARTIAL"
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) rebuildOneVcenterCache(ctx context.Context, vcURL string) (int, int, int, error) {
|
||||
vc := vcenter.New(h.Logger, h.VcCreds)
|
||||
if err := vc.Login(vcURL); err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("unable to connect to vcenter: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
logoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := vc.Logout(logoutCtx); err != nil {
|
||||
h.Logger.Warn("vcenter cache rebuild logout failed", "vcenter", vcURL, "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
folderLookup, err := vc.BuildFolderPathLookup()
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("failed to build folder cache from vcenter: %w", err)
|
||||
}
|
||||
resourcePools, err := vc.BuildResourcePoolLookup()
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("failed to build resource pool cache from vcenter: %w", err)
|
||||
}
|
||||
hostLookup, err := vc.BuildHostLookup()
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("failed to build host cache from vcenter: %w", err)
|
||||
}
|
||||
|
||||
dbConn := h.Database.DB()
|
||||
if err := db.ClearVcenterReferenceCache(ctx, dbConn, vcURL); err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("failed to clear existing vcenter cache rows: %w", err)
|
||||
}
|
||||
lastSeen := time.Now().Unix()
|
||||
|
||||
folderCount := 0
|
||||
for folderRef, folderPath := range folderLookup {
|
||||
if err := db.UpsertVcenterFolderCache(ctx, dbConn, vcURL, folderRef, folderPath, lastSeen); err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("failed to persist folder cache: %w", err)
|
||||
}
|
||||
folderCount++
|
||||
}
|
||||
|
||||
poolCount := 0
|
||||
for poolRef, poolName := range resourcePools {
|
||||
if err := db.UpsertVcenterResourcePoolCache(ctx, dbConn, vcURL, poolRef, poolName, lastSeen); err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("failed to persist resource pool cache: %w", err)
|
||||
}
|
||||
poolCount++
|
||||
}
|
||||
|
||||
hostCount := 0
|
||||
for hostRef, entry := range hostLookup {
|
||||
if err := db.UpsertVcenterHostCache(ctx, dbConn, vcURL, hostRef, entry.Cluster, entry.Datacenter, lastSeen); err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("failed to persist host cache: %w", err)
|
||||
}
|
||||
hostCount++
|
||||
}
|
||||
|
||||
return folderCount, poolCount, hostCount, nil
|
||||
}
|
||||
@@ -59,3 +59,22 @@ type SnapshotRepairSuiteResponse struct {
|
||||
MonthlyRefined string `json:"monthly_refined"`
|
||||
MonthlyFailed string `json:"monthly_failed"`
|
||||
}
|
||||
|
||||
// VcenterCacheRebuildResult describes rebuild results for a single vCenter.
|
||||
type VcenterCacheRebuildResult struct {
|
||||
Vcenter string `json:"vcenter"`
|
||||
FolderEntries int `json:"folder_entries"`
|
||||
ResourcePoolEntries int `json:"resource_pool_entries"`
|
||||
HostEntries int `json:"host_entries"`
|
||||
DurationSeconds float64 `json:"duration_seconds"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// VcenterCacheRebuildResponse describes the vCenter object cache rebuild response.
|
||||
type VcenterCacheRebuildResponse struct {
|
||||
Status string `json:"status"`
|
||||
Total int `json:"total"`
|
||||
Succeeded int `json:"succeeded"`
|
||||
Failed int `json:"failed"`
|
||||
Results []VcenterCacheRebuildResult `json:"results"`
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ func New(logger *slog.Logger, database db.Database, buildTime string, sha1ver st
|
||||
mux.HandleFunc("/vcenters/totals", h.VcenterTotals)
|
||||
mux.HandleFunc("/vcenters/totals/daily", h.VcenterTotalsDaily)
|
||||
mux.HandleFunc("/vcenters/totals/hourly", h.VcenterTotalsHourlyDetailed)
|
||||
mux.HandleFunc("/api/vcenters/cache/rebuild", h.VcenterCacheRebuild)
|
||||
mux.HandleFunc("/metrics", h.Metrics)
|
||||
|
||||
mux.HandleFunc("/snapshots/hourly", h.SnapshotHourlyList)
|
||||
|
||||
Reference in New Issue
Block a user