api/controller/cleanup_upload_delete.go
Rama main · réplica de lectura del repositorio original.
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"
"github.com/jinzhu/gorm"
)
var uploadInspectionOperation = regexp.MustCompile(`^[A-Za-z0-9_-]{1,128}$`)
var uploadInspectionFingerprint = regexp.MustCompile(`^[a-f0-9]{64}$`)
func systemUploadStorageBinding() (string, error) { return storage.Default().UploadChunkBinding() }
func systemDeleteUploadChunks(id string) error {
configured := storage.Default()
if configured == nil || configured.StorageCli == nil {
return guard.ErrCoordinationUnavailable
}
return configured.StorageCli.CleanupChunks(id)
}
func (h *CleanupCoordinationHandler) verifyUploadWriters(ctx context.Context, database *gorm.DB) error {
if h.inspectReferenceWriters == nil {
return guard.ErrCoordinationUnavailable
}
writers, err := h.inspectReferenceWriters(ctx)
if err != nil {
return guard.ErrCoordinationUnavailable
}
ready, err := guard.UploadWriterCoverageRegistered(database, writers)
if err != nil || !ready {
return guard.ErrCoordinationUnavailable
}
return nil
}
// DeleteUploadChunks is internal to the trusted Console. Its route requires
// CleanupIdentity; Console must separately validate enterprise ownership and
// the user's confirmed selection. No caller-controlled filesystem path is used.
func (h *CleanupCoordinationHandler) DeleteUploadChunks(w http.ResponseWriter, r *http.Request) {
var body struct {
OperationID string `json:"operation_id"`
EventID string `json:"event_id"`
SessionID string `json:"session_id"`
StateFingerprint string `json:"state_fingerprint"`
StorageFingerprint string `json:"storage_fingerprint"`
IdleDays int `json:"idle_days"`
}
if !coordinationDecode(w, r, &body) {
return
}
if body.IdleDays < 1 || body.IdleDays > 365 || !uploadInventoryEventID.MatchString(body.EventID) || !uploadInventoryEventID.MatchString(body.SessionID) {
httputil.ReturnError(r, w, 400, "INVALID_UPLOAD_SELECTION")
return
}
if h.database == nil || h.uploadStorageBinding == nil || h.deleteUploadChunks == nil {
coordinationError(w, r, guard.ErrCoordinationUnavailable)
return
}
database := h.database()
if database == nil {
coordinationError(w, r, guard.ErrCoordinationUnavailable)
return
}
verify := func() error {
if err := h.verifyUploadWriters(r.Context(), database); err != nil {
return err
}
binding, err := h.uploadStorageBinding()
if err != nil || binding == "" || binding != body.StorageFingerprint {
return guard.ErrCoordinationChanged
}
return nil
}
if err := verify(); err != nil {
coordinationError(w, r, err)
return
}
var session model.UploadSession
if err := database.Where("id = ? AND event_id = ?", body.SessionID, body.EventID).First(&session).Error; err != nil {
coordinationError(w, r, guard.ErrCoordinationChanged)
return
}
if session.ExpiresAt.IsZero() || session.ExpiresAt.After(time.Now().Add(-time.Duration(body.IdleDays)*24*time.Hour)) {
coordinationError(w, r, guard.ErrCoordinationChanged)
return
}
err := guard.DeleteExpiredUploadChunks(database, body.OperationID, body.EventID, body.SessionID, body.StateFingerprint, func(id string) error {
// Recheck current deployment and configured storage after the durable tombstone.
if err := verify(); err != nil {
return err
}
return h.deleteUploadChunks(id)
})
if err != nil {
coordinationError(w, r, err)
return
}
httputil.ReturnSuccess(r, w, map[string]interface{}{"protocol": 1, "operation_id": body.OperationID, "event_id": body.EventID, "session_id": body.SessionID, "state": "deleted"})
}
// InspectUploadChunks reads the authoritative receipt for one original
// operation. It never invokes the retirement or deletion paths.
func (h *CleanupCoordinationHandler) InspectUploadChunks(w http.ResponseWriter, r *http.Request) {
var body struct {
OperationID string `json:"operation_id"`
EventID string `json:"event_id"`
SessionID string `json:"session_id"`
StateFingerprint string `json:"state_fingerprint"`
StorageFingerprint string `json:"storage_fingerprint"`
}
if !coordinationDecode(w, r, &body) {
return
}
if !uploadInspectionOperation.MatchString(body.OperationID) || !uploadInventoryEventID.MatchString(body.EventID) ||
!uploadInventoryEventID.MatchString(body.SessionID) || !uploadInspectionFingerprint.MatchString(body.StateFingerprint) ||
!uploadInspectionFingerprint.MatchString(body.StorageFingerprint) {
httputil.ReturnError(r, w, 400, "INVALID_UPLOAD_INSPECTION")
return
}
if h.database == nil || h.uploadStorageBinding == nil || h.measureUploadChunks == nil {
coordinationError(w, r, guard.ErrCoordinationUnavailable)
return
}
database := h.database()
if database == nil {
coordinationError(w, r, guard.ErrCoordinationUnavailable)
return
}
state := "unknown"
binding, err := h.uploadStorageBinding()
if err == nil && binding != "" && binding == body.StorageFingerprint {
state, err = guard.InspectExpiredUploadDeletion(database, body.OperationID, body.EventID, body.SessionID, body.StateFingerprint,
func(id string) (int64, int, error) {
usage, measureErr := h.measureUploadChunks(r.Context(), id)
return usage.Bytes, usage.Objects, measureErr
})
if err != nil {
coordinationError(w, r, err)
return
}
}
httputil.ReturnSuccess(r, w, map[string]interface{}{"protocol": 1, "operation_id": body.OperationID, "event_id": body.EventID, "session_id": body.SessionID, "state": state})
}