Files
seaweedfs/weed/shell/command_ec_scrub.go
T
Chris LuandClaude Opus 5.5 80e0bf97dd shell: dial volume servers on their registered gRPC port in volume.scrub, ec.scrub, volume.server.state (#12315)
* shell: dial scrubbed volume servers on their registered gRPC port

volume.scrub, ec.scrub and volume.server.state built each data node's
address from its HTTP address alone, so a volume server whose gRPC port
is not the HTTP port + 10000 was dialed on the wrong port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* shell: resolve -node volume servers to their registered gRPC port

An explicit -node/-nodes list was dialed on HTTP port + 10000 too.
Match each entry against the topology and take the data node's gRPC
port; an address the master does not know is used as given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* shell: keep a gRPC port given in -node

A node given as host:port.grpcPort was rewritten to the registered gRPC
port; only a bare host:port takes it from the topology.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* shell: dial -node volume servers when the topology is unavailable

An explicit node list did not need the master before; keep using it as
given if the volume list cannot be fetched. Selecting every node still
requires the topology.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* shell: bound the topology lookup for an explicit -node list

With the master down, volumeServer.state -nodes waited on the master
forever instead of reaching the given volume servers. Give the lookup a
30s deadline and fall back to the addresses as given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* shell: resolve volume server addresses from a topology-only listing

Only the data nodes are needed to find each server's gRPC port, so ask
the master for the topology without its volumes and ec shards. A large
cluster no longer risks running past the -node lookup deadline and
falling back to the HTTP port + 10000.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* shell: skip the topology lookup when every -node names its gRPC port

A host:port.grpcPort entry is dialed as given, so a list made only of
those does not need the master and no longer waits on it during an
outage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 18:50:48 -08:00

98 lines
3.2 KiB
Go

package shell
import (
"flag"
"fmt"
"io"
"strconv"
"strings"
"github.com/seaweedfs/seaweedfs/weed/ec"
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
)
func init() {
Commands = append(Commands, &commandEcVolumeScrub{})
}
type commandEcVolumeScrub struct {
}
func (c *commandEcVolumeScrub) Name() string {
return "ec.scrub"
}
func (c *commandEcVolumeScrub) Help() string {
return `scrubs EC volume contents on volume servers.
Supports either scrubbing only needle data, or deep scrubbing file contents as well.
Scrubbing can be limited to specific EC volume IDs for specific volume servers.
By default, all volume IDs across all servers are processed.
`
}
func (c *commandEcVolumeScrub) HasTag(CommandTag) bool {
return false
}
func (c *commandEcVolumeScrub) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
volScrubCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
nodesStr := volScrubCommand.String("node", "", "comma-separated list of volume server <host>:<port> (optional)")
volumeIDsStr := volScrubCommand.String("volumeId", "", "comma-separated EC volume IDs to process (optional)")
mode := volScrubCommand.String("mode", "local", "scrubbing mode (index/local/full/reads/checksum)")
maxParallelization := volScrubCommand.Int("maxParallelization", DefaultMaxParallelization, "run up to X tasks in parallel, whenever possible")
showDetails := volScrubCommand.Bool("details", false, "display scrub result details, if available")
forceDeletedNeedlesCheck := volScrubCommand.Bool("forceDeletedNeedlesCheck", false, "force strict verification of deleted needles (full and reads modes only); may report false positives when EC indexes disagree")
if err = volScrubCommand.Parse(args); err != nil {
return err
}
if err = commandEnv.confirmIsLocked(args); err != nil {
return
}
volumeServerAddrs, err := collectVolumeServerAddresses(commandEnv, *nodesStr)
if err != nil {
return err
}
volumeIDs := []uint32{}
if *volumeIDsStr != "" {
for _, vids := range strings.Split(*volumeIDsStr, ",") {
vids = strings.TrimSpace(vids)
if vids == "" {
continue
}
if vid, err := strconv.ParseUint(vids, 10, 32); err == nil {
volumeIDs = append(volumeIDs, uint32(vid))
} else {
return fmt.Errorf("invalid volume ID %q", vids)
}
}
}
var scrubMode volume_server_pb.VolumeScrubMode
switch strings.ToUpper(*mode) {
case "INDEX":
scrubMode = volume_server_pb.VolumeScrubMode_INDEX
case "LOCAL":
scrubMode = volume_server_pb.VolumeScrubMode_LOCAL
case "FULL":
scrubMode = volume_server_pb.VolumeScrubMode_FULL
case "CHECKSUM":
scrubMode = volume_server_pb.VolumeScrubMode_CHECKSUM
case "READS":
scrubMode = volume_server_pb.VolumeScrubMode_READS
default:
return fmt.Errorf("unsupported scrubbing mode %q", *mode)
}
fmt.Fprintf(writer, "using %s mode\n", scrubMode.String())
if *forceDeletedNeedlesCheck &&
scrubMode != volume_server_pb.VolumeScrubMode_FULL && scrubMode != volume_server_pb.VolumeScrubMode_READS {
return fmt.Errorf("deleted needle checks are only supported for FULL and READS scrubs")
}
return ec.ScrubEcVolumes(commandEnv.ecEnv(), writer, volumeServerAddrs, volumeIDs, scrubMode, *forceDeletedNeedlesCheck, *maxParallelization, *showDetails)
}