Ctrl K
ComunidadGuías…

api/controller/cleanup_upload_inventory.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package controller

import (
	"context"
	"net/http"
	"regexp"
	"time"

	"github.com/goodrain/rainbond/db/model"
	guard "github.com/goodrain/rainbond/pkg/cleanup"
	"github.com/goodrain/rainbond/pkg/component/storage"
	httputil "github.com/goodrain/rainbond/util/http"
)

var uploadInventoryEventID = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$`)

type uploadInventoryItem struct {
	StateFingerprint string    `json:"state_fingerprint"`
	ID               string    `json:"id"`
	EventID          string    `json:"event_id"`
	FileName         string    `json:"file_name"`
	Status           string    `json:"status"`
	CreatedAt        time.Time `json:"created_at"`
	UpdatedAt        time.Time `json:"updated_at"`
	ExpiresAt        time.Time `json:"expires_at"`
	Bytes            *int64    `json:"bytes"`
	Objects          *int      `json:"objects"`
	SizeStatus       string    `json:"size_status"`
}

func systemMeasureUploadChunks(ctx context.Context, id string) (storage.UploadChunkUsage, error) {
	configured := storage.Default()
	if configured == nil {
		return storage.UploadChunkUsage{}, guard.ErrCoordinationUnavailable
	}
	measurer, ok := configured.StorageCli.(interface {
		MeasureUploadChunks(context.Context, string) (storage.UploadChunkUsage, error)
	})
	if !ok {
		return storage.UploadChunkUsage{}, guard.ErrCoordinationUnavailable
	}
	return measurer.MeasureUploadChunks(ctx, id)
}

type uploadPackageInventoryItem struct {
	Referenced         *bool  `json:"referenced"`
	ReferencesComplete bool   `json:"references_complete"`
	EventID            string `json:"event_id"`
	Bytes              *int64 `json:"bytes"`
	Objects            *int   `json:"objects"`
	SizeStatus         string `json:"size_status"`
}

func systemMeasureUploadEvent(ctx context.Context, id string) (storage.UploadChunkUsage, error) {
	configured := storage.Default()
	if configured == nil {
		return storage.UploadChunkUsage{}, guard.ErrCoordinationUnavailable
	}
	measurer, ok := configured.StorageCli.(interface {
		MeasureUploadEvent(context.Context, string) (storage.UploadChunkUsage, error)
	})
	if !ok {
		return storage.UploadChunkUsage{}, guard.ErrCoordinationUnavailable
	}
	return measurer.MeasureUploadEvent(ctx, id)
}

// UploadInventory observes chunks of explicitly requested upload events. It is
// internal to the trusted Console and requires CleanupIdentity on its route.
// Console must derive event IDs from its own enterprise/team records. Neither
// expiration nor this measurement proves that an upload is safe to delete.
func (h *CleanupCoordinationHandler) UploadInventory(w http.ResponseWriter, r *http.Request) {
	var body struct {
		EventIDs []string `json:"event_ids"`
	}
	if !coordinationDecode(w, r, &body) {
		return
	}
	seen := map[string]bool{}
	if len(body.EventIDs) == 0 || len(body.EventIDs) > 50 {
		httputil.ReturnError(r, w, 400, "INVALID_UPLOAD_SCOPE")
		return
	}
	for _, id := range body.EventIDs {
		if !uploadInventoryEventID.MatchString(id) || seen[id] {
			httputil.ReturnError(r, w, 400, "INVALID_UPLOAD_SCOPE")
			return
		}
		seen[id] = true
	}
	if h.database == nil || h.measureUploadChunks == nil {
		coordinationError(w, r, guard.ErrCoordinationUnavailable)
		return
	}
	database := h.database()
	if database == nil {
		coordinationError(w, r, guard.ErrCoordinationUnavailable)
		return
	}
	rows := []model.UploadSession{}
	if err := database.Select("id, event_id, file_name, status, uploaded_chunks, storage_path, created_at, updated_at, expires_at").Where("event_id IN (?)", body.EventIDs).Order("id ASC").Limit(501).Find(&rows).Error; err != nil {
		coordinationError(w, r, guard.ErrCoordinationUnavailable)
		return
	}
	if len(rows) > 500 {
		httputil.ReturnError(r, w, 413, "UPLOAD_INVENTORY_LIMIT")
		return
	}
	ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
	defer cancel()
	items := make([]uploadInventoryItem, 0, len(rows))
	for _, row := range rows {
		item := uploadInventoryItem{StateFingerprint: guard.UploadSessionFingerprint(row), ID: row.ID, EventID: row.EventID, FileName: row.FileName, Status: row.Status, CreatedAt: row.CreatedAt, UpdatedAt: row.UpdatedAt, ExpiresAt: row.ExpiresAt, SizeStatus: "unavailable"}
		if ctx.Err() == nil {
			usage, err := h.measureUploadChunks(ctx, row.ID)
			if err == nil && usage.Bytes >= 0 && usage.Objects >= 0 {
				item.Bytes = &usage.Bytes
				item.Objects = &usage.Objects
				item.SizeStatus = "measured"
			}
		}
		items = append(items, item)
	}
	packages := make([]uploadPackageInventoryItem, 0, len(body.EventIDs))
	var references map[string]bool
	if h.uploadPackageReferences != nil {
		references, _ = h.uploadPackageReferences(database, body.EventIDs)
	}

	// Measure an event once, even if it has multiple upload/retry sessions.
	// Legacy non-chunk uploads may have no UploadSession row at all.
	for _, eventID := range body.EventIDs {
		item := uploadPackageInventoryItem{EventID: eventID, ReferencesComplete: references != nil, SizeStatus: "unavailable"}
		if used, ok := references[eventID]; ok {
			item.Referenced = &used
		}
		if h.measureUploadEvent != nil && ctx.Err() == nil {
			usage, err := h.measureUploadEvent(ctx, eventID)
			if err == nil && usage.Bytes >= 0 && usage.Objects >= 0 {
				item.Bytes = &usage.Bytes
				item.Objects = &usage.Objects
				item.SizeStatus = "measured"
			}
		}
		packages = append(packages, item)
	}

	binding := ""
	if h.uploadStorageBinding != nil {
		binding, _ = h.uploadStorageBinding()
	}
	writersReady := false
	if h.inspectReferenceWriters != nil {
		writers, err := h.inspectReferenceWriters(ctx)
		if err == nil {
			writersReady, _ = guard.UploadWriterCoverageRegistered(database, writers)
		}
	}
	httputil.ReturnSuccess(r, w, struct {
		StorageFingerprint string                       `json:"storage_fingerprint"`
		WritersReady       bool                         `json:"writers_ready"`
		Packages           []uploadPackageInventoryItem `json:"packages"`
		Protocol           int                          `json:"protocol"`
		Scope              string                       `json:"scope"`
		Items              []uploadInventoryItem        `json:"items"`
	}{binding, writersReady, packages, 1, "upload_chunks", items})
}