mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-08 15:41:15 +02:00
* fix(master): honour -volume.fileSizeLimitMB on the master's /submit - #6748 `weed server -volume.fileSizeLimitMB=2048` still refused anything over 256MB, and the reason is not the one the report assumes: the option does reach the volume server. The master does not use it. Uploads through the master's /submit are buffered by submitForClientHandler, which passed a hardcoded 256MB to needle.ParseUpload, so the master rejected what the volume server it started would have accepted. The limit is now passed in. `weed master` gains its own -fileSizeLimitMB with the same 256 default, so a standalone master behaves exactly as before, and `weed server` and `weed mini` hand it the value their volume server already got. * master.follower: take the same upload limit, and say which flag to match Review found the follower left behind. It serves /submit like the leader and buffers uploads under the same limit, but kept the fixed 256MB, so a cluster raised above that would accept an upload through the leader and refuse the identical one through a follower. Two smaller points from the same review: the master's flag description named only the standalone volume server's spelling, and now names the weed server and weed mini form too; and the under-limit test asserted on the error message alone, so it would have passed had the limit rejected that payload with different wording. It now requires the request to get past parsing.
237 lines
8.4 KiB
Go
237 lines
8.4 KiB
Go
package weed_server
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/rand/v2"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/util/version"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/backend/memory_map"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/super_block"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/types"
|
|
"github.com/seaweedfs/seaweedfs/weed/topology"
|
|
util_http "github.com/seaweedfs/seaweedfs/weed/util/http"
|
|
)
|
|
|
|
func (ms *MasterServer) collectionDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
|
collectionName := r.FormValue("collection")
|
|
if _, ok := ms.Topo.FindCollection(collectionName); !ok {
|
|
writeJsonError(w, r, http.StatusBadRequest, fmt.Errorf("collection %s does not exist", collectionName))
|
|
return
|
|
}
|
|
|
|
// The same two passes the gRPC CollectionDelete runs, rather than a second
|
|
// copy of the volume-server walk: this handler deleted only the normal
|
|
// volumes and left the collection's EC shards behind.
|
|
if err := ms.deleteCollection(r.Context(), collectionName); err != nil {
|
|
writeJsonError(w, r, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
func (ms *MasterServer) dirStatusHandler(w http.ResponseWriter, r *http.Request) {
|
|
m := make(map[string]interface{})
|
|
m["Version"] = version.Version()
|
|
m["TopologyId"] = ms.Topo.GetTopologyId()
|
|
m["Topology"] = ms.Topo.ToInfo()
|
|
writeJsonQuiet(w, r, http.StatusOK, m)
|
|
}
|
|
|
|
func (ms *MasterServer) volumeVacuumHandler(w http.ResponseWriter, r *http.Request) {
|
|
gcString := r.FormValue("garbageThreshold")
|
|
gcThreshold := ms.option.GarbageThreshold
|
|
if gcString != "" {
|
|
var err error
|
|
gcThreshold, err = strconv.ParseFloat(gcString, 32)
|
|
if err != nil {
|
|
glog.V(0).Infof("garbageThreshold %s is not a valid float number: %v", gcString, err)
|
|
writeJsonError(w, r, http.StatusNotAcceptable, fmt.Errorf("garbageThreshold %s is not a valid float number", gcString))
|
|
return
|
|
}
|
|
}
|
|
// glog.Infoln("garbageThreshold =", gcThreshold)
|
|
ms.Topo.Vacuum(ms.grpcDialOption, gcThreshold, ms.option.MaxParallelVacuumPerServer, 0, "", ms.preallocateSize, false)
|
|
ms.dirStatusHandler(w, r)
|
|
}
|
|
|
|
func (ms *MasterServer) volumeGrowHandler(w http.ResponseWriter, r *http.Request) {
|
|
count := uint64(0)
|
|
option, err := ms.getVolumeGrowOption(r)
|
|
if err != nil {
|
|
writeJsonError(w, r, http.StatusNotAcceptable, err)
|
|
return
|
|
}
|
|
glog.V(0).Infof("volumeGrowHandler received %v from %v", option.String(), r.RemoteAddr)
|
|
|
|
if count, err = strconv.ParseUint(r.FormValue("count"), 10, 32); err == nil {
|
|
replicaCount := int64(count * uint64(option.ReplicaPlacement.GetCopyCount()))
|
|
if ms.Topo.AvailableSpaceFor(option) < replicaCount {
|
|
err = fmt.Errorf("only %d volumes left, not enough for %d", ms.Topo.AvailableSpaceFor(option), replicaCount)
|
|
} else if !ms.Topo.DataCenterExists(option.DataCenter) {
|
|
err = fmt.Errorf("data center %v not found in topology", option.DataCenter)
|
|
} else {
|
|
var newVidLocations []*master_pb.VolumeLocation
|
|
newVidLocations, err = ms.vg.GrowByCountAndType(ms.grpcDialOption, uint32(count), option, ms.Topo)
|
|
if len(newVidLocations) > 0 {
|
|
ms.broadcastVolumeLocationsToClients(newVidLocations)
|
|
}
|
|
count = uint64(len(newVidLocations))
|
|
}
|
|
} else {
|
|
err = fmt.Errorf("can not parse parameter count %s", r.FormValue("count"))
|
|
}
|
|
|
|
if err != nil {
|
|
writeJsonError(w, r, http.StatusNotAcceptable, err)
|
|
} else {
|
|
writeJsonQuiet(w, r, http.StatusOK, map[string]interface{}{"count": count})
|
|
}
|
|
}
|
|
|
|
func (ms *MasterServer) volumeStatusHandler(w http.ResponseWriter, r *http.Request) {
|
|
m := make(map[string]interface{})
|
|
m["Version"] = version.Version()
|
|
m["Volumes"] = ms.Topo.ToVolumeMap()
|
|
writeJsonQuiet(w, r, http.StatusOK, m)
|
|
}
|
|
|
|
func (ms *MasterServer) redirectHandler(w http.ResponseWriter, r *http.Request) {
|
|
vid, _, _, _, _ := parseURLPath(r.URL.Path)
|
|
collection := r.FormValue("collection")
|
|
location := ms.findVolumeLocation(collection, vid)
|
|
if location.Error == "" {
|
|
loc := location.Locations[rand.IntN(len(location.Locations))]
|
|
url, _ := util_http.NormalizeUrl(loc.PublicUrl)
|
|
if r.URL.RawQuery != "" {
|
|
url = url + r.URL.Path + "?" + r.URL.RawQuery
|
|
} else {
|
|
url = url + r.URL.Path
|
|
}
|
|
http.Redirect(w, r, url, http.StatusPermanentRedirect)
|
|
} else {
|
|
writeJsonError(w, r, http.StatusNotFound, fmt.Errorf("volume id %s not found: %s", vid, location.Error))
|
|
}
|
|
}
|
|
|
|
func (ms *MasterServer) submitFromMasterServerHandler(w http.ResponseWriter, r *http.Request) {
|
|
fileSizeLimitBytes := int64(ms.option.FileSizeLimitMB) * 1024 * 1024
|
|
if ms.Topo.IsLeader() {
|
|
submitForClientHandler(w, r, func(ctx context.Context) pb.ServerAddress { return ms.option.Master }, ms.grpcDialOption, fileSizeLimitBytes)
|
|
} else {
|
|
masterUrl, err := ms.Topo.Leader()
|
|
if err != nil {
|
|
writeJsonError(w, r, http.StatusInternalServerError, err)
|
|
} else {
|
|
submitForClientHandler(w, r, func(ctx context.Context) pb.ServerAddress { return masterUrl }, ms.grpcDialOption, fileSizeLimitBytes)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ms *MasterServer) getVolumeGrowOption(r *http.Request) (*topology.VolumeGrowOption, error) {
|
|
replicationString := r.FormValue("replication")
|
|
if replicationString == "" {
|
|
replicationString = ms.option.DefaultReplicaPlacement
|
|
}
|
|
replicaPlacement, err := super_block.NewReplicaPlacementFromString(replicationString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ttl, err := needle.ReadTTL(r.FormValue("ttl"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
memoryMapMaxSizeMb, err := memory_map.ReadMemoryMapMaxSizeMb(r.FormValue("memoryMapMaxSizeMb"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
diskType := types.ToDiskType(r.FormValue("disk"))
|
|
|
|
preallocate := ms.preallocateSize
|
|
if r.FormValue("preallocate") != "" {
|
|
preallocate, err = strconv.ParseInt(r.FormValue("preallocate"), 10, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to parse int64 preallocate = %s: %v", r.FormValue("preallocate"), err)
|
|
}
|
|
}
|
|
ver := needle.GetCurrentVersion()
|
|
volumeGrowOption := &topology.VolumeGrowOption{
|
|
Collection: r.FormValue("collection"),
|
|
ReplicaPlacement: replicaPlacement,
|
|
Ttl: ttl,
|
|
DiskType: diskType,
|
|
Preallocate: preallocate,
|
|
DataCenter: r.FormValue("dataCenter"),
|
|
Rack: r.FormValue("rack"),
|
|
DataNode: r.FormValue("dataNode"),
|
|
MemoryMapMaxSizeMb: memoryMapMaxSizeMb,
|
|
Version: uint32(ver),
|
|
}
|
|
return volumeGrowOption, nil
|
|
}
|
|
|
|
func (ms *MasterServer) collectionInfoHandler(w http.ResponseWriter, r *http.Request) {
|
|
//get collection from request
|
|
collectionName := r.FormValue("collection")
|
|
if collectionName == "" {
|
|
writeJsonError(w, r, http.StatusBadRequest, fmt.Errorf("collection is required"))
|
|
return
|
|
}
|
|
//output details of the volumes?
|
|
detail := r.FormValue("detail") == "true"
|
|
//collect collection info
|
|
collection, ok := ms.Topo.FindCollection(collectionName)
|
|
if !ok {
|
|
writeJsonError(w, r, http.StatusBadRequest, fmt.Errorf("collection %s does not exist", collectionName))
|
|
return
|
|
}
|
|
|
|
volumeLayouts := collection.GetAllVolumeLayouts()
|
|
|
|
if detail {
|
|
//prepare the json response
|
|
all_stats := make([]map[string]interface{}, len(volumeLayouts))
|
|
for i, volumeLayout := range volumeLayouts {
|
|
volumeLayoutStats := volumeLayout.Stats()
|
|
m := make(map[string]interface{})
|
|
m["Version"] = version.Version()
|
|
m["Collection"] = collectionName
|
|
m["TotalSize"] = volumeLayoutStats.TotalSize
|
|
m["FileCount"] = volumeLayoutStats.FileCount
|
|
m["UsedSize"] = volumeLayoutStats.UsedSize
|
|
all_stats[i] = m
|
|
}
|
|
//write it
|
|
writeJsonQuiet(w, r, http.StatusOK, all_stats)
|
|
} else {
|
|
//prepare the json response
|
|
collectionStats := map[string]interface{}{
|
|
"Version": version.Version(),
|
|
"Collection": collectionName,
|
|
"TotalSize": uint64(0),
|
|
"FileCount": uint64(0),
|
|
"UsedSize": uint64(0),
|
|
"VolumeCount": uint64(0),
|
|
}
|
|
for _, volumeLayout := range volumeLayouts {
|
|
volumeLayoutStats := volumeLayout.Stats()
|
|
collectionStats["TotalSize"] = collectionStats["TotalSize"].(uint64) + volumeLayoutStats.TotalSize
|
|
collectionStats["FileCount"] = collectionStats["FileCount"].(uint64) + volumeLayoutStats.FileCount
|
|
collectionStats["UsedSize"] = collectionStats["UsedSize"].(uint64) + volumeLayoutStats.UsedSize
|
|
collectionStats["VolumeCount"] = collectionStats["VolumeCount"].(uint64) + 1
|
|
}
|
|
//write it
|
|
writeJsonQuiet(w, r, http.StatusOK, collectionStats)
|
|
}
|
|
}
|