mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-24 15:00:43 +02:00
* shell: discover the filer on first use instead of at startup The startup retry loop made every weed shell invocation wait out the connect timeout on clusters that have no filer, even for commands that only talk to the master. Discovery now runs once, on the first command that actually needs a filer, keeping the retry-until-registered fix for the fresh-cluster race without stalling master-only commands. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * shell: stop filer discovery retries on non-transient errors A master that keeps answering ListClusterNodes with a permanent error (permission denied, unimplemented) cannot gain a filer by retrying, so discovery returns instead of polling until the deadline. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * shell: explain an empty filer list when discovery times out * shell: retry filer discovery while the address is still empty Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
128 lines
3.5 KiB
Go
128 lines
3.5 KiB
Go
package shell
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/filer"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/remote_storage"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
)
|
|
|
|
func init() {
|
|
Commands = append(Commands, &commandRemoteUnmount{})
|
|
}
|
|
|
|
type commandRemoteUnmount struct {
|
|
}
|
|
|
|
func (c *commandRemoteUnmount) Name() string {
|
|
return "remote.unmount"
|
|
}
|
|
|
|
func (c *commandRemoteUnmount) Help() string {
|
|
return `unmount remote storage
|
|
|
|
# assume a remote storage is configured to name "s3_1"
|
|
remote.configure -name=s3_1 -type=s3 -s3.access_key=xxx -s3.secret_key=yyy
|
|
# mount and pull one bucket
|
|
remote.mount -dir=/xxx -remote=s3_1/bucket
|
|
|
|
# unmount the mounted directory and remove its cache
|
|
remote.unmount -dir=/xxx
|
|
|
|
`
|
|
}
|
|
|
|
func (c *commandRemoteUnmount) HasTag(CommandTag) bool {
|
|
return false
|
|
}
|
|
|
|
func (c *commandRemoteUnmount) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
|
|
|
|
remoteMountCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
|
|
|
dir := remoteMountCommand.String("dir", "", "a directory in filer")
|
|
|
|
if err = remoteMountCommand.Parse(args); err != nil {
|
|
return nil
|
|
}
|
|
|
|
mappings, listErr := filer.ReadMountMappings(commandEnv.option.GrpcDialOption, commandEnv.FilerAddress())
|
|
if listErr != nil {
|
|
return listErr
|
|
}
|
|
if *dir == "" {
|
|
return jsonPrintln(writer, mappings)
|
|
}
|
|
|
|
_, found := mappings.Mappings[*dir]
|
|
if !found {
|
|
return fmt.Errorf("directory %s is not mounted", *dir)
|
|
}
|
|
|
|
// delete the mount mapping first: the filer reloads it on the spot, so the
|
|
// purge below stays local instead of deleting the remote objects
|
|
fmt.Fprintf(writer, "deleting mount for %s ...\n", *dir)
|
|
if err = filer.DeleteMountMapping(commandEnv, *dir); err != nil {
|
|
return fmt.Errorf("delete mount mapping: %w", err)
|
|
}
|
|
|
|
// purge mounted data
|
|
fmt.Fprintf(writer, "purge %s ...\n", *dir)
|
|
if err = c.purgeMountedData(commandEnv, *dir); err != nil {
|
|
return fmt.Errorf("purge mounted data: %w", err)
|
|
}
|
|
|
|
// reset remote sync offset in case the folder is mounted again
|
|
if err = remote_storage.SetSyncOffset(commandEnv.option.GrpcDialOption, commandEnv.FilerAddress(), *dir, time.Now().UnixNano()); err != nil {
|
|
return fmt.Errorf("reset remote.sync offset for %s: %v", *dir, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *commandRemoteUnmount) purgeMountedData(commandEnv *CommandEnv, dir string) error {
|
|
|
|
// find existing directory, and ensure the directory is empty
|
|
err := commandEnv.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
|
ctx := context.Background()
|
|
parent, name := util.FullPath(dir).DirAndName()
|
|
lookupResp, lookupErr := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
|
|
Directory: parent,
|
|
Name: name,
|
|
})
|
|
if lookupErr != nil {
|
|
return fmt.Errorf("lookup %s: %v", dir, lookupErr)
|
|
}
|
|
|
|
oldEntry := lookupResp.Entry
|
|
|
|
deleteError := filer_pb.DoRemove(ctx, client, parent, name, true, true, true, false, nil)
|
|
if deleteError != nil {
|
|
return fmt.Errorf("delete %s: %v", dir, deleteError)
|
|
}
|
|
|
|
mkdirErr := filer_pb.DoMkdir(ctx, client, parent, name, func(entry *filer_pb.Entry) {
|
|
entry.Attributes = oldEntry.Attributes
|
|
entry.Extended = oldEntry.Extended
|
|
entry.Attributes.Crtime = time.Now().Unix()
|
|
entry.Attributes.Mtime = time.Now().Unix()
|
|
})
|
|
if mkdirErr != nil {
|
|
return fmt.Errorf("mkdir %s: %v", dir, mkdirErr)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|