mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-13 15:29:12 +01:00
BatchDelete now applies the replica check the HTTP delete path already uses: when the master lists fewer replicas than the volume's copy count, the delete is refused with 503 instead of applied to the replicas that remain. The caller retries, so the replica that was offline still gets the delete once it returns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
173 lines
4.9 KiB
Go
173 lines
4.9 KiB
Go
package weed_server
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/operation"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
|
"github.com/seaweedfs/seaweedfs/weed/topology"
|
|
)
|
|
|
|
func (vs *VolumeServer) BatchDelete(ctx context.Context, req *volume_server_pb.BatchDeleteRequest) (*volume_server_pb.BatchDeleteResponse, error) {
|
|
resp := &volume_server_pb.BatchDeleteResponse{}
|
|
|
|
if err := vs.checkGrpcAdminAuth(ctx); err != nil {
|
|
return resp, err
|
|
}
|
|
|
|
if err := vs.CheckMaintenanceMode(); err != nil {
|
|
return resp, err
|
|
}
|
|
|
|
now := uint64(time.Now().Unix())
|
|
replicaErrs := make(map[needle.VolumeId]error)
|
|
|
|
for _, fid := range req.FileIds {
|
|
vid, id_cookie, err := operation.ParseFileId(fid)
|
|
if err != nil {
|
|
resp.Results = append(resp.Results, &volume_server_pb.DeleteResult{
|
|
FileId: fid,
|
|
Status: http.StatusBadRequest,
|
|
Error: err.Error()})
|
|
continue
|
|
}
|
|
|
|
n := new(needle.Needle)
|
|
volumeId, _ := needle.NewVolumeId(vid)
|
|
if req.SkipCookieCheck {
|
|
n.Id, _, err = needle.ParseNeedleIdCookie(id_cookie)
|
|
if err != nil {
|
|
resp.Results = append(resp.Results, &volume_server_pb.DeleteResult{
|
|
FileId: fid,
|
|
Status: http.StatusBadRequest,
|
|
Error: err.Error()})
|
|
continue
|
|
}
|
|
} else {
|
|
if err := n.ParsePath(id_cookie); err != nil {
|
|
resp.Results = append(resp.Results, &volume_server_pb.DeleteResult{
|
|
FileId: fid,
|
|
Status: http.StatusBadRequest,
|
|
Error: err.Error()})
|
|
continue
|
|
}
|
|
}
|
|
|
|
if err := vs.checkDeleteReplicas(volumeId, replicaErrs); err != nil {
|
|
resp.Results = append(resp.Results, &volume_server_pb.DeleteResult{
|
|
FileId: fid,
|
|
Status: http.StatusServiceUnavailable,
|
|
Error: err.Error(),
|
|
})
|
|
continue
|
|
}
|
|
ecVolume, isEcVolume := vs.store.FindEcVolume(volumeId)
|
|
if !req.SkipCookieCheck {
|
|
cookie := n.Cookie
|
|
if !isEcVolume {
|
|
if _, err := vs.store.ReadVolumeNeedle(volumeId, n, deleteReadOption(), nil); err != nil {
|
|
resp.Results = append(resp.Results, &volume_server_pb.DeleteResult{
|
|
FileId: fid,
|
|
Status: http.StatusNotFound,
|
|
Error: err.Error(),
|
|
})
|
|
continue
|
|
}
|
|
} else {
|
|
if _, err := vs.store.ReadEcShardNeedle(volumeId, n, nil); err != nil {
|
|
resp.Results = append(resp.Results, &volume_server_pb.DeleteResult{
|
|
FileId: fid,
|
|
Status: http.StatusNotFound,
|
|
Error: err.Error(),
|
|
})
|
|
continue
|
|
}
|
|
}
|
|
if n.Cookie != cookie {
|
|
resp.Results = append(resp.Results, &volume_server_pb.DeleteResult{
|
|
FileId: fid,
|
|
Status: http.StatusBadRequest,
|
|
Error: "File Random Cookie does not match.",
|
|
})
|
|
continue
|
|
}
|
|
}
|
|
|
|
if n.IsChunkedManifest() {
|
|
resp.Results = append(resp.Results, &volume_server_pb.DeleteResult{
|
|
FileId: fid,
|
|
Status: http.StatusNotAcceptable,
|
|
Error: "ChunkManifest: not allowed in batch delete mode.",
|
|
})
|
|
continue
|
|
}
|
|
|
|
n.LastModified = now
|
|
if !isEcVolume {
|
|
if size, err := vs.store.DeleteVolumeNeedle(volumeId, n); err != nil {
|
|
resp.Results = append(resp.Results, &volume_server_pb.DeleteResult{
|
|
FileId: fid,
|
|
Status: http.StatusInternalServerError,
|
|
Error: err.Error()},
|
|
)
|
|
} else if size == 0 {
|
|
resp.Results = append(resp.Results, &volume_server_pb.DeleteResult{
|
|
FileId: fid,
|
|
Status: http.StatusNotModified},
|
|
)
|
|
} else {
|
|
resp.Results = append(resp.Results, &volume_server_pb.DeleteResult{
|
|
FileId: fid,
|
|
Status: http.StatusAccepted,
|
|
Size: uint32(size)},
|
|
)
|
|
}
|
|
} else {
|
|
size, err := vs.store.DeleteEcShardNeedle(ecVolume, n, n.Cookie)
|
|
if errors.Is(err, storage.ErrorDeleted) {
|
|
// Already gone, which is what the caller asked for. The
|
|
// non-EC branch above reports that as StatusNotModified.
|
|
resp.Results = append(resp.Results, &volume_server_pb.DeleteResult{
|
|
FileId: fid,
|
|
Status: http.StatusNotModified},
|
|
)
|
|
} else if err != nil {
|
|
resp.Results = append(resp.Results, &volume_server_pb.DeleteResult{
|
|
FileId: fid,
|
|
Status: http.StatusInternalServerError,
|
|
Error: err.Error()},
|
|
)
|
|
} else {
|
|
resp.Results = append(resp.Results, &volume_server_pb.DeleteResult{
|
|
FileId: fid,
|
|
Status: http.StatusAccepted,
|
|
Size: uint32(size)},
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
// checkDeleteReplicas refuses a delete the master cannot route to every
|
|
// replica, as the HTTP delete path does, so an offline replica does not keep
|
|
// the needle once it returns.
|
|
func (vs *VolumeServer) checkDeleteReplicas(volumeId needle.VolumeId, checked map[needle.VolumeId]error) error {
|
|
if err, found := checked[volumeId]; found {
|
|
return err
|
|
}
|
|
var err error
|
|
if v := vs.store.GetVolume(volumeId); v != nil && v.ReplicaPlacement.GetCopyCount() > 1 {
|
|
_, err = topology.GetRemoteReplications(vs.store, vs.grpcDialOption, volumeId, vs.GetMaster)
|
|
}
|
|
checked[volumeId] = err
|
|
return err
|
|
}
|