mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-13 23:39:33 +01:00
* shell: return flag parse errors from every command 48 commands returned nil when their flags failed to parse, so a mistyped flag printed the flag error but the command still counted as a success: a piped weed shell run exited 0, the master maintenance loop logged no error, and admin script jobs reported the line as done. Return the parse error as s3.bucket.create (#12544) and the other shell commands already do. TestCommandsReturnFlagParseErrors runs every registered command with an unknown flag and fails for the 48 on master. Signed-off-by: anant <170962632+antcybersec@users.noreply.github.com> * shell: don't report -h as a failure With the parse errors returned, -h on those commands now returns flag.ErrHelp, so the shell printed "error: flag: help requested" and a piped run exited 1. The flag package has already printed the usage, so processEachCmd now treats flag.ErrHelp as success for every command. Signed-off-by: anant <170962632+antcybersec@users.noreply.github.com> * shell: keep the -h test beside the flag parse test TestProcessEachCmdTreatsHelpAsSuccess covers the same change as TestCommandsReturnFlagParseErrors, so it lives in the same new file and shell_liner_test.go stays as it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Signed-off-by: anant <170962632+antcybersec@users.noreply.github.com> Co-authored-by: anant <170962632+antcybersec@users.noreply.github.com> Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
159 lines
5.2 KiB
Go
159 lines
5.2 KiB
Go
package shell
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/operation"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/super_block"
|
|
"github.com/seaweedfs/seaweedfs/weed/util/wildcard"
|
|
)
|
|
|
|
func init() {
|
|
Commands = append(Commands, &commandVolumeConfigureReplication{})
|
|
}
|
|
|
|
type commandVolumeConfigureReplication struct {
|
|
}
|
|
|
|
func (c *commandVolumeConfigureReplication) Name() string {
|
|
return "volume.configure.replication"
|
|
}
|
|
|
|
func (c *commandVolumeConfigureReplication) Help() string {
|
|
return `change volume replication value
|
|
|
|
This command changes a volume replication value. It should be followed by "volume.fix.replication".
|
|
|
|
Up to -maxParallelization volume servers are configured at a time, one volume at a time on each,
|
|
and replicas of the same volume on different servers take turns.
|
|
|
|
`
|
|
}
|
|
|
|
func (c *commandVolumeConfigureReplication) HasTag(CommandTag) bool {
|
|
return false
|
|
}
|
|
|
|
func (c *commandVolumeConfigureReplication) Do(args []string, commandEnv *CommandEnv, _ io.Writer) (err error) {
|
|
|
|
configureReplicationCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
|
volumeIdInt := configureReplicationCommand.Int("volumeId", 0, "the volume id")
|
|
replicationString := configureReplicationCommand.String("replication", "", "the intended replication value")
|
|
collectionPattern := configureReplicationCommand.String("collectionPattern", "", "comma-separated collection names, with '*' and '?' wildcards; empty matches all")
|
|
maxParallelization := configureReplicationCommand.Int("maxParallelization", DefaultMaxParallelization, "run up to X tasks in parallel, whenever possible")
|
|
if err = configureReplicationCommand.Parse(args); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = commandEnv.confirmIsLocked(args); err != nil {
|
|
return
|
|
}
|
|
|
|
if *replicationString == "" {
|
|
return fmt.Errorf("empty replication value")
|
|
}
|
|
|
|
replicaPlacement, err := super_block.NewReplicaPlacementFromString(*replicationString)
|
|
if err != nil {
|
|
return fmt.Errorf("replication format: %w", err)
|
|
}
|
|
|
|
// collect topology information
|
|
topologyInfo, _, err := collectTopologyInfo(commandEnv, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
vid := needle.VolumeId(*volumeIdInt)
|
|
volumeFilter, err := getVolumeFilter(replicaPlacement, uint32(vid), *collectionPattern)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return configureReplicationOnVolumeServers(commandEnv, topologyInfo, volumeFilter, replicaPlacement.String(), *maxParallelization)
|
|
}
|
|
|
|
func configureReplicationOnVolumeServers(commandEnv *CommandEnv, topologyInfo *master_pb.TopologyInfo, volumeFilter func(*master_pb.VolumeInformationMessage) bool, replication string, maxParallelization int) error {
|
|
type serverVolumes struct {
|
|
addr pb.ServerAddress
|
|
volumeIds []uint32
|
|
}
|
|
var servers []serverVolumes
|
|
// VolumeConfigure unmounts and remounts the volume, so replicas take turns
|
|
// to keep at least one of them serving.
|
|
volumeLocks := make(map[uint32]*sync.Mutex)
|
|
|
|
// find all data nodes with volumes that needs replication change
|
|
eachDataNode(topologyInfo, func(dc DataCenterId, rack RackId, dn *master_pb.DataNodeInfo) {
|
|
var targetVolumeIds []uint32
|
|
for _, diskInfo := range dn.DiskInfos {
|
|
for _, v := range diskInfo.VolumeInfos {
|
|
if volumeFilter(v) {
|
|
targetVolumeIds = append(targetVolumeIds, v.Id)
|
|
if volumeLocks[v.Id] == nil {
|
|
volumeLocks[v.Id] = &sync.Mutex{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if len(targetVolumeIds) > 0 {
|
|
servers = append(servers, serverVolumes{pb.NewServerAddressFromDataNode(dn), targetVolumeIds})
|
|
}
|
|
})
|
|
|
|
ewg := NewErrorWaitGroup(maxParallelization)
|
|
for _, server := range servers {
|
|
ewg.Add(func() error {
|
|
err := operation.WithVolumeServerClient(false, server.addr, commandEnv.option.GrpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
|
|
for _, targetVolumeId := range server.volumeIds {
|
|
volumeLocks[targetVolumeId].Lock()
|
|
resp, configureErr := volumeServerClient.VolumeConfigure(context.Background(), &volume_server_pb.VolumeConfigureRequest{
|
|
VolumeId: targetVolumeId,
|
|
Replication: replication,
|
|
})
|
|
volumeLocks[targetVolumeId].Unlock()
|
|
if configureErr != nil {
|
|
return configureErr
|
|
}
|
|
if resp.Error != "" {
|
|
return errors.New(resp.Error)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("volume server %s: %w", server.addr, err)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
return ewg.Wait()
|
|
}
|
|
|
|
func getVolumeFilter(replicaPlacement *super_block.ReplicaPlacement, volumeId uint32, collectionPattern string) (func(message *master_pb.VolumeInformationMessage) bool, error) {
|
|
replicaPlacementInt32 := uint32(replicaPlacement.Byte())
|
|
if volumeId > 0 {
|
|
return func(v *master_pb.VolumeInformationMessage) bool {
|
|
return v.Id == volumeId && v.ReplicaPlacement != replicaPlacementInt32
|
|
}, nil
|
|
}
|
|
collectionMatcher, err := wildcard.CompileCollectionMatcher(collectionPattern)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return func(v *master_pb.VolumeInformationMessage) bool {
|
|
return collectionMatcher.Matches(v.Collection) && v.ReplicaPlacement != replicaPlacementInt32
|
|
}, nil
|
|
}
|