Files
seaweedfs/weed/topology/cluster_commands.go
T
Chris Lu 2874bd05fb Freeze volume growth on topology id mismatch (#11763) (#11820)
* pb: ResumeVolumeGrowth rpc for operator-driven growth resume (#11763)

* master: freeze volume growth on topology id mismatch (#11763)

* shell: volume.growth.resume command (#11763)

* master: replicate the growth freeze and gate it on veteran heartbeats (#11763)

* master: block allocation while the topology id is being minted

DeferAllocation only armed after the topology id persisted, so growth
requests arriving during init ran ahead of the warmup window. Gate
NextVolumeId on an init-pending flag spanning the whole mint.

* master: stash reported topology ids until the local id is minted (#11763)

* drain stashed topology id reports when the id arrives outside the mint path

* persist the growth-freeze flag in the mirror file and raft snapshots

* log hashicorp raft marshal and apply errors in applyGrowthFreeze
2026-10-20 07:34:31 -07:00

75 lines
2.1 KiB
Go

package topology
import (
"encoding/json"
"fmt"
hashicorpRaft "github.com/hashicorp/raft"
"github.com/seaweedfs/raft"
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
)
type MaxVolumeIdCommand struct {
MaxVolumeId needle.VolumeId `json:"maxVolumeId"`
TopologyId string `json:"topologyId"`
// GrowthFrozen replicates the volume-growth freeze through raft so a
// leader change does not silently lift it. nil means no change.
GrowthFrozen *bool `json:"growthFrozen,omitempty"`
}
func NewMaxVolumeIdCommand(value needle.VolumeId, topologyId string) *MaxVolumeIdCommand {
return &MaxVolumeIdCommand{
MaxVolumeId: value,
TopologyId: topologyId,
}
}
func (c *MaxVolumeIdCommand) CommandName() string {
return "MaxVolumeId"
}
// deprecatedCommandApply represents the old interface to apply a command to the server.
func (c *MaxVolumeIdCommand) Apply(server raft.Server) (interface{}, error) {
topo := server.Context().(*Topology)
before := topo.GetMaxVolumeId()
topo.UpAdjustMaxVolumeId(c.MaxVolumeId)
if c.TopologyId != "" {
prevTopologyId := topo.GetTopologyId()
topo.SetTopologyId(c.TopologyId)
// Log when TopologyId is set for the first time, with different messages for leader and follower.
if prevTopologyId == "" {
if server.State() == raft.Leader {
glog.V(0).Infof("TopologyId generated and applied on leader: %s", c.TopologyId)
} else {
glog.V(0).Infof("TopologyId applied on follower: %s", c.TopologyId)
}
}
}
if c.GrowthFrozen != nil {
topo.SetGrowthFrozen(*c.GrowthFrozen)
}
if err := topo.PersistMaxVolumeId(c.MaxVolumeId); err != nil {
glog.Warningf("persist max volume id %d: %v", c.MaxVolumeId, err)
}
glog.V(1).Infoln("max volume id", before, "==>", topo.GetMaxVolumeId())
return nil, nil
}
func (s *MaxVolumeIdCommand) Persist(sink hashicorpRaft.SnapshotSink) error {
b, err := json.Marshal(s)
if err != nil {
return fmt.Errorf("marshal: %w", err)
}
_, err = sink.Write(b)
if err != nil {
sink.Cancel()
return fmt.Errorf("sink.Write(): %w", err)
}
return sink.Close()
}
func (s *MaxVolumeIdCommand) Release() {
}