All checks were successful
continuous-integration/drone/push Build is passing
163 lines
5.3 KiB
Go
163 lines
5.3 KiB
Go
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.WithoutCancel(ctx), 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
|
|
}
|