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 }