diff --git a/core/aws_test.go b/core/aws_test.go index 65aa804..61c810a 100644 --- a/core/aws_test.go +++ b/core/aws_test.go @@ -42,7 +42,7 @@ func (s *AwsTest) SetUpSuite(t *C) { func (s *AwsTest) TestRegionDetection(t *C) { s.s3.bucket = "goofys-eu-west-1.kahing.xyz" - if TigrisDetected(s.s3.flags) { + if TigrisDetectedForTests(s.s3.flags) { t.Skip("Not relevant for Tigris detected") } @@ -55,7 +55,7 @@ func (s *AwsTest) TestRegionDetection(t *C) { func (s *AwsTest) TestBucket404(t *C) { s.s3.bucket = RandStringBytesMaskImprSrc(63) - if TigrisDetected(s.s3.flags) { + if TigrisDetectedForTests(s.s3.flags) { t.Skip("Not relevant for Tigris detected") } diff --git a/core/backend.go b/core/backend.go index f456bba..03b57c9 100644 --- a/core/backend.go +++ b/core/backend.go @@ -28,8 +28,9 @@ import ( type Capabilities struct { MaxMultipartSize uint64 // indicates that the blob store has native support for directories - DirBlob bool - Name string + DirBlob bool + Name string + IsTigris bool } type HeadBlobInput struct { diff --git a/core/backend_s3.go b/core/backend_s3.go index fa768c9..c4875c0 100644 --- a/core/backend_s3.go +++ b/core/backend_s3.go @@ -332,6 +332,10 @@ func (s *S3Backend) detectBucketLocationByHEAD() (err error, isAws bool) { isAws = true } + if server != nil && strings.Contains(server[0], "Tigris") { + s.cap.IsTigris = true + } + switch resp.StatusCode { case 200: // note that this only happen if the bucket is in us-east-1 @@ -408,7 +412,7 @@ func (s *S3Backend) Init(key string) error { } if !s.config.RegionSet { - _, _ = s.detectBucketLocationByHEAD() + _, isAws = s.detectBucketLocationByHEAD() // if err == nil { // we detected a region header, this is probably AWS S3, // or we can use anonymous access, or both @@ -809,7 +813,46 @@ func (s *S3Backend) DeleteBlobs(param *DeleteBlobsInput) (*DeleteBlobsOutput, er } func (s *S3Backend) RenameBlob(param *RenameBlobInput) (*RenameBlobOutput, error) { - return nil, syscall.ENOTSUP + from := s.bucket + "/" + param.Source + + params := &s3.CopyObjectInput{ + Bucket: &s.bucket, + CopySource: aws.String(pathEscape(from)), + Key: ¶m.Destination, + MetadataDirective: aws.String(s3.MetadataDirectiveCopy), + } + + S3Debug(s3Log, params, "RenameObject") + + if s.config.UseSSE { + params.ServerSideEncryption = &s.sseType + if s.config.UseKMS && s.config.KMSKeyID != "" { + params.SSEKMSKeyId = &s.config.KMSKeyID + } + } else if s.config.SseC != "" { + params.SSECustomerAlgorithm = PString("AES256") + params.SSECustomerKey = &s.config.SseC + params.SSECustomerKeyMD5 = &s.config.SseCDigest + params.CopySourceSSECustomerAlgorithm = PString("AES256") + params.CopySourceSSECustomerKey = &s.config.SseC + params.CopySourceSSECustomerKeyMD5 = &s.config.SseCDigest + } + + if s.config.ACL != "" { + params.ACL = &s.config.ACL + } + + req, _ := s.CopyObjectRequest(params) + + withHeader(req, "X-Tigris-Rename", "true") + + err := req.Send() + if err != nil { + s3Log.Warn().Interface("params", params).Err(err).Msg("RenameObject failed") + return nil, err + } + + return &RenameBlobOutput{s.getRequestId(req)}, nil } func (s *S3Backend) mpuCopyPart(from string, to string, mpuId string, bytes string, part int64, srcEtag *string) (*string, error) { diff --git a/core/cfg/config.go b/core/cfg/config.go index 68bcf62..ebb6f3e 100644 --- a/core/cfg/config.go +++ b/core/cfg/config.go @@ -131,6 +131,9 @@ type FlagStorage struct { TigrisPrefetch bool TigrisListContent bool + TigrisRename bool + + IsTigris bool } func (flags *FlagStorage) GetMimeType(fileName string) (retMime *string) { @@ -163,9 +166,9 @@ func (flags *FlagStorage) Cleanup() { } } -func (flags *FlagStorage) IsTigris() bool { - return strings.Contains(flags.Endpoint, "tigris.dev") || - strings.Contains(flags.Endpoint, "storage.dev") +func (flags *FlagStorage) IsTigrisEndpoint() { + flags.IsTigris = strings.Contains(flags.Endpoint, ".tigris.dev") || + strings.Contains(flags.Endpoint, ".storage.dev") } var defaultHTTPTransport = http.Transport{ diff --git a/core/cfg/flags.go b/core/cfg/flags.go index cd92902..06fd320 100644 --- a/core/cfg/flags.go +++ b/core/cfg/flags.go @@ -147,16 +147,6 @@ MISC OPTIONS: Usage: "Drop root group and change to this group ID (defaults to --gid).", }, - cli.BoolFlag{ - Name: "tigris-prefetch", - Usage: "Enable Tigris prefetch on list (default: off)", - }, - - cli.BoolFlag{ - Name: "tigris-list-content", - Usage: "Include inlined objects content in list (default: on)", - }, - cli.BoolFlag{ Name: "refresh-dirs", Usage: "Automatically refresh open directories using notifications under Windows", @@ -660,6 +650,21 @@ MISC OPTIONS: Value: 512, Usage: "Simultaneously opened cache file descriptor limit", }, + + cli.BoolFlag{ + Name: "tigris-prefetch", + Usage: "Enable Tigris prefetch on list (default: off)", + }, + + cli.BoolFlag{ + Name: "tigris-list-content", + Usage: "Include inlined objects content in list (default: on)", + }, + + cli.BoolFlag{ + Name: "no-instant-rename", + Usage: "Disable Tigris 'instant' rename (default: off)", + }, } if runtime.GOOS == "windows" { @@ -956,6 +961,7 @@ func PopulateFlags(c *cli.Context) (ret *FlagStorage) { ClusterGrpcReflection: c.Bool("grpc-reflection"), TigrisPrefetch: c.Bool("tigris-prefetch"), + TigrisRename: !c.Bool("no-instant-rename"), TigrisListContent: c.Bool("tigris-list-content"), } @@ -1001,8 +1007,10 @@ func PopulateFlags(c *cli.Context) (ret *FlagStorage) { panic("Unknown --iam-flavor: " + config.IAMFlavor) } + flags.IsTigrisEndpoint() + // special enabled for the Tigris by default - if flags.IsTigris() { + if flags.IsTigris { flags.EnableSpecials = !c.IsSet("no-specials") } @@ -1140,5 +1148,6 @@ func DefaultFlags() *FlagStorage { }, TigrisPrefetch: false, TigrisListContent: true, + TigrisRename: true, } } diff --git a/core/cfg/logger.go b/core/cfg/logger.go index f4f48ca..755b3ec 100644 --- a/core/cfg/logger.go +++ b/core/cfg/logger.go @@ -42,12 +42,12 @@ func InitLoggers(flags *FlagStorage) error { Format: flags.LogFormat, } - if (lib.IsTTY(os.Stdout) || lib.IsTTY(os.Stderr)) && log.DefaultLogConfig.Format == "" && lf == "stderr" { + if (lib.IsTTY(os.Stdout) || lib.IsTTY(os.Stderr)) && log.DefaultLogConfig.Format == "" && (lf == "stderr" || lf == "syslog") { log.DefaultLogConfig.Format = "console" } log.DefaultLogConfig.Color = true - if flags.NoLogColor { + if flags.NoLogColor || lf == "syslog" { log.DefaultLogConfig.Color = false } diff --git a/core/file.go b/core/file.go index 1a53326..f91e8e6 100644 --- a/core/file.go +++ b/core/file.go @@ -768,14 +768,145 @@ func (inode *Inode) sendUpload(priority int) bool { return false } -func (inode *Inode) sendRename() { - cloud, key := inode.cloud() - if inode.isDir() { - key += "/" +func (inode *Inode) finishErrorSendRenameSpecial(err error, from string, key string, oldParent *Inode, oldName string) { + mappedErr := mapAwsError(err) + if mappedErr == syscall.ENOENT || mappedErr == syscall.ERANGE { + s3Log.Warnf("Conflict detected (inode %v): failed to copy %v to %v: %v. File is removed remotely, dropping cache", inode.Id, from, key, err) + inode.mu.Lock() + newParent := inode.Parent + oldParent := inode.oldParent + oldName := inode.oldName + inode.oldParent = nil + inode.oldName = "" + inode.renamingTo = false + inode.resetCache() + inode.mu.Unlock() + newParent.removeChild(inode) + if oldParent != nil { + oldParent.mu.Lock() + if _, ok := oldParent.dir.DeletedChildren[oldName]; ok { + delete(oldParent.dir.DeletedChildren, oldName) + oldParent.addModified(-1) + } + oldParent.mu.Unlock() + } + } else { + fuseLog.Warnf("Failed to copy %v to %v (rename): %v", from, key, err) + inode.mu.Lock() + inode.recordFlushError(err) + if inode.Parent == oldParent && inode.Name == oldName { + // Someone renamed the inode back to the original name + // ...while we failed to copy it :) + inode.oldParent = nil + inode.oldName = "" + inode.renamingTo = false + inode.Parent.addModified(-1) + if (inode.CacheState == ST_MODIFIED || inode.CacheState == ST_CREATED) && + !inode.isStillDirty() { + inode.SetCacheState(ST_CACHED) + inode.SetAttrTime(time.Now()) + } + } + inode.mu.Unlock() + } +} + +func (inode *Inode) finishSuccessSendRenameSpecial(from string, key string, oldParent *Inode, oldName string, newParent *Inode, newName string) { + fuseLog.Debugf("Renamed %v to %v (rename)", from, key) + inode.mu.Lock() + + // Now we know that the object is accessible by the new name + if inode.Parent == newParent && inode.Name == newName { + // Just clear the old path + inode.oldParent = nil + inode.oldName = "" + } else if inode.Parent == oldParent && inode.Name == oldName { + // Someone renamed the inode back to the original name(!) + inode.oldParent = nil + inode.oldName = "" + // Delete the new key instead of the old one (?) + } else { + // Someone renamed the inode again(!) + inode.oldParent = newParent + inode.oldName = newName } + if (inode.CacheState == ST_MODIFIED || inode.CacheState == ST_CREATED) && + !inode.isStillDirty() { + inode.SetCacheState(ST_CACHED) + inode.SetAttrTime(time.Now()) + } + inode.renamingTo = false + inode.mu.Unlock() + + oldParent.mu.Lock() + delete(oldParent.dir.DeletedChildren, oldName) + oldParent.mu.Unlock() + // And track ModifiedChildren because rename is special - it takes two parents + oldParent.addModified(-1) +} + +func (inode *Inode) finishRenameFlush() { + inode.mu.Lock() + inode.IsFlushing -= inode.fs.flags.MaxParallelParts + atomic.AddInt64(&inode.fs.activeFlushers, -1) + inode.fs.WakeupFlusher() + inode.mu.Unlock() +} + +func (inode *Inode) startRenameFlush() { inode.IsFlushing += inode.fs.flags.MaxParallelParts atomic.AddInt64(&inode.fs.stats.flushes, 1) atomic.AddInt64(&inode.fs.activeFlushers, 1) +} + +func (inode *Inode) sendRenameSpecial() { + if inode.isDir() && inode.fs.flags.NoDirObject { + return + } + + cloud, key := inode.cloud() + _, from := inode.oldParent.cloud() + + from = appendChildName(from, inode.oldName) + inode.renamingTo = true + oldParent := inode.oldParent + oldName := inode.oldName + newParent := inode.Parent + newName := inode.Name + if inode.isDir() { + from += "/" + key += "/" + } + + inode.startRenameFlush() + + go func() { + inode.fs.addInflightChange(key) + _, err := cloud.RenameBlob(&RenameBlobInput{ + Source: from, + Destination: key, + }) + inode.fs.completeInflightChange(key) + if err != nil { + mappedErr := mapAwsError(err) + if mappedErr != syscall.ENOENT || !inode.isDir() { + inode.finishErrorSendRenameSpecial(err, from, key, oldParent, oldName) + } else { + inode.finishSuccessSendRenameSpecial(from, key, oldParent, oldName, newParent, newName) + } + } else { + inode.finishSuccessSendRenameSpecial(from, key, oldParent, oldName, newParent, newName) + } + inode.finishRenameFlush() + }() +} + +func (inode *Inode) sendRenameCopy() { + cloud, key := inode.cloud() + if inode.isDir() { + key += "/" + } + inode.startRenameFlush() _, from := inode.oldParent.cloud() from = appendChildName(from, inode.oldName) oldParent := inode.oldParent @@ -914,14 +1045,20 @@ func (inode *Inode) sendRename() { } } } - inode.mu.Lock() - inode.IsFlushing -= inode.fs.flags.MaxParallelParts - atomic.AddInt64(&inode.fs.activeFlushers, -1) - inode.fs.WakeupFlusher() - inode.mu.Unlock() + inode.finishRenameFlush() }() } +func (inode *Inode) sendRename() { + flags := inode.fs.flags + cloud := *inode.fs.cloud.Load() + if cloud.Capabilities().IsTigris && flags.TigrisRename { + inode.sendRenameSpecial() + return + } + inode.sendRenameCopy() +} + func (inode *Inode) sendUpdateMeta() { // Update metadata by COPYing into the same object // It results in the optimized implementation in S3 diff --git a/core/goofys_common_test.go b/core/goofys_common_test.go index 96a0313..6fc039a 100644 --- a/core/goofys_common_test.go +++ b/core/goofys_common_test.go @@ -282,7 +282,7 @@ func (s *GoofysTest) waitForEmulator(t *C, addr string) { func (s *GoofysTest) deleteBucket(cloud StorageBackend) error { var err error // FIXME: Tigris returns 500 for RemoveBucket. Skip it for now. - if s.isLocalTigris { + if s.isTigris { return nil } for i := 0; i < 5; i++ { @@ -537,7 +537,7 @@ func (s *GoofysTest) SetUpTest(t *C) { s.cloud = NewS3BucketEventualConsistency(s3) } - if s.emulator && !TigrisDetected(flags) { + if s.emulator && !TigrisDetectedForTests(flags) { s3.Handlers.Sign.Clear() s3.Handlers.Sign.PushBack(SignV2) s3.Handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler) @@ -656,8 +656,8 @@ func (s *GoofysTest) SetUpTest(t *C) { t.Fatal("Unsupported backend") } - s.isTigris = TigrisDetected(flags) - s.isLocalTigris = LocalTigrisDetected(flags) + s.isTigris = TigrisDetectedForTests(flags) + s.isLocalTigris = LocalTigrisDetectedForTests(flags) testLog.Infof("Tigris detected: %v, local: %v", s.isTigris, s.isLocalTigris) if s.isLocalTigris { @@ -668,6 +668,14 @@ func (s *GoofysTest) SetUpTest(t *C) { _, err := s.cloud.MakeBucket(&MakeBucketInput{}) t.Assert(err, IsNil) s.removeBucket = append(s.removeBucket, s.cloud) + } else { + resp, err := s.cloud.ListBlobs(&ListBlobsInput{}) + t.Assert(err, IsNil) + for _, item := range resp.Items { + + _, err = s.cloud.DeleteBlob(&DeleteBlobInput{Key: *item.Key}) + t.Assert(err, IsNil) + } } s.setupDefaultEnv(t, "") diff --git a/core/goofys_unix_test.go b/core/goofys_unix_test.go index b4a83d2..6887863 100644 --- a/core/goofys_unix_test.go +++ b/core/goofys_unix_test.go @@ -26,6 +26,7 @@ import ( "os" "os/exec" "runtime" + "strings" "sync" "syscall" "time" @@ -40,7 +41,7 @@ import ( func (s *GoofysTest) mountCommon(t *C, mountPoint string, sameProc bool) { err := os.MkdirAll(mountPoint, 0o700) - if err == syscall.EEXIST { + if err != nil && (err == syscall.EEXIST || strings.Contains(err.Error(), "file exists")) { err = nil } t.Assert(err, IsNil) diff --git a/core/tigris_test.go b/core/tigris_test.go index 1d37912..bc1c916 100644 --- a/core/tigris_test.go +++ b/core/tigris_test.go @@ -35,7 +35,7 @@ var ( localTigris bool ) -func tigrisDetected(flags *cfg.FlagStorage) (bool, bool) { +func testTigrisDetected(flags *cfg.FlagStorage) (bool, bool) { if triedDetect { return detected, localTigris } @@ -52,20 +52,24 @@ func tigrisDetected(flags *cfg.FlagStorage) (bool, bool) { return false, local } - triedDetect = true localTigris = local - detected = r.StatusCode == http.StatusOK && strings.Contains(r.Header.Get("Server"), "Tigris") + detected = strings.HasSuffix(endpoint, ".tigris.dev") || strings.HasSuffix(endpoint, ".storage.dev") + + triedDetect = true + if !detected { + detected = r.StatusCode == http.StatusOK && strings.Contains(r.Header.Get("Server"), "Tigris") + } return detected, local } -func LocalTigrisDetected(flags *cfg.FlagStorage) bool { - t, local := tigrisDetected(flags) +func LocalTigrisDetectedForTests(flags *cfg.FlagStorage) bool { + t, local := testTigrisDetected(flags) return t && local } -func TigrisDetected(flags *cfg.FlagStorage) bool { - t, _ := tigrisDetected(flags) +func TigrisDetectedForTests(flags *cfg.FlagStorage) bool { + t, _ := testTigrisDetected(flags) return t } @@ -77,7 +81,7 @@ func TestListIncludeMetadataAndContent(t *testing.T) { flags.Backend = &conf flags.TigrisListContent = true - if !TigrisDetected(flags) { + if !TigrisDetectedForTests(flags) { t.Skip("Tigris not detected") } diff --git a/log/logger.go b/log/logger.go index 265c500..25177a6 100644 --- a/log/logger.go +++ b/log/logger.go @@ -198,7 +198,7 @@ func NewLogger(config *LogConfig, module string, colorized bool, writer io.Write var logger zerolog.Logger if config.Format == "console" { output := zerolog.ConsoleWriter{ - Out: os.Stdout, + Out: writer, TimeFormat: time.StampMicro, } output.NoColor = !colorized diff --git a/test/cases/tigris_rename.sh b/test/cases/tigris_rename.sh new file mode 100644 index 0000000..edd29fe --- /dev/null +++ b/test/cases/tigris_rename.sh @@ -0,0 +1,192 @@ +#!/bin/bash +# Copyright 2025 Tigris Data, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -exo pipefail + +. "$(dirname "$0")/../mount.sh" + +CASE=tigris_rename +KEY="$CASE/$RANDOM" +DIR="$MNT_DIR/$KEY" +TMP_DIR=/tmp/$KEY +size=32768 +n_iter=1 + +export AWS_PROFILE=local + +mount() { + # shellcheck disable=SC2086 + FS_BIN=$(dirname "$0")/../../tigrisfs _mount $@ + sleep 5 +} + +test_copy() { + mount "$MNT_DIR" --no-instant-rename --debug_s3 --log-file=./test-tigris-copy.log --log-format=console --no-log-color + + mkdir -p "$DIR" + mkdir -p "$TMP_DIR" + + n=$RANDOM + for i in $(seq 1 $n_iter); do + fn="$DIR/file.$n.$i" + fntmp="$TMP_DIR/file.$n.$i" + head -c $size < /dev/urandom > "$fntmp" + cp "$fntmp" "$fn" + done + + start=$(date +%s%3N) + + for i in $(seq 1 $n_iter); do + fn="$DIR/file.$n.$i" + mv "$fn" "$fn.tmp" + done + + sync + + end=$(date +%s%3N) + echo "Duration0: $((end - start)) ms" + + #for i in $(seq 1 10); do + # fn="$DIR/file.$i" + # cat "$fn.$i.tmp" > /dev/null + #done + + # Remount without specials enabled + _umount "$MNT_DIR" + + end=$(date +%s%3N) + echo "Duration: $((end - start)) ms" + + for i in $(seq 1 $n_iter); do + fn="file.$n.$i" + aws s3api get-object --key "$KEY/$fn.tmp" --bucket "$BUCKET_NAME" "$TMP_DIR/$fn.tmp" + diff -u "$TMP_DIR/$fn.tmp" "$TMP_DIR/$fn" + done +} + +test_rename_one() { + mount "$MNT_DIR" --debug_s3 --log-file=./test-tigris-rename.log --log-format=console --no-log-color + + mkdir -p "$DIR" + mkdir -p "$TMP_DIR" + + n=$RANDOM + for i in $(seq 1 $n_iter); do + fn="$DIR/file.$n.$i" + fntmp="$TMP_DIR/file.$n.$i" + head -c $size < /dev/urandom > "${fntmp}" + cp "$fntmp" "$fn" + done + + start=$(date +%s%3N) + + for i in $(seq 1 $n_iter); do + fn="$DIR/file.$n.$i" + mv "${fn}" "${fn}.tmp" + done + + sync + + end=$(date +%s%3N) + echo "Duration0: $((end - start)) ms" + + _umount "$MNT_DIR" + + for i in $(seq 1 $n_iter); do + fn="file.$n.$i" + aws s3api get-object --key "$KEY/$fn.tmp" --bucket "$BUCKET_NAME" "$TMP_DIR/$fn.tmp" + diff -u "$TMP_DIR/$fn.tmp" "$TMP_DIR/$fn" + done + + end=$(date +%s%3N) + echo "Duration: $((end - start)) ms" +} + +test_rename_nested() { + mount "$MNT_DIR" --debug_fuse --pprof 8889 --debug_s3 --log-file=./test-tigris-rename.log --log-format=console --no-log-color + + n=$RANDOM + DIR_NESTED="$DIR/$n/nested" + DIR_NESTED_2="$DIR_NESTED/nested2" + TMP_DIR_NESTED="$TMP_DIR/$n/nested" + TMP_DIR_NESTED_2="$TMP_DIR/$n/nested/nested2" + + mkdir -p "$DIR_NESTED" "$DIR_NESTED_2" + mkdir -p "$TMP_DIR_NESTED" "$TMP_DIR_NESTED_2" + + for i in $(seq 1 $n_iter); do + fn="$DIR_NESTED/file.$n.$i" + fntmp="$TMP_DIR_NESTED/file.$n.$i" + head -c $size < /dev/urandom > "${fntmp}" + cp "$fntmp" "$fn" + done + + for i in $(seq 1 $n_iter); do + fn="$DIR_NESTED_2/file.$n.$i" + fntmp="$TMP_DIR_NESTED_2/file.$n.$i" + head -c $size < /dev/urandom > "${fntmp}" + cp "$fntmp" "$fn" + done + + start=$(date +%s%3N) + + sync + sleep 5 + + tree "$DIR" + + DIR_NESTED_3="$DIR/nested3" + mv "$DIR_NESTED" "$DIR_NESTED_3" + + tree "$DIR" + + sync + sleep 5 + + end=$(date +%s%3N) + echo "Duration0: $((end - start)) ms" + + _umount "$MNT_DIR" + + for i in $(seq 1 $n_iter); do + fn="$KEY/nested3/file.$n.$i" + fntmp="$TMP_DIR_NESTED/file.$n.$i" + aws s3api get-object --key "$fn" --bucket "$BUCKET_NAME" "$TMP_DIR/tmp0" + diff -u "$TMP_DIR/tmp0" "$fntmp" + done + + for i in $(seq 1 $n_iter); do + fn="$KEY/nested3/nested2/file.$n.$i" + fntmp="$TMP_DIR_NESTED_2/file.$n.$i" + aws s3api get-object --key "$fn" --bucket "$BUCKET_NAME" "$TMP_DIR/tmp1" + diff -u "$TMP_DIR/tmp1" "$fntmp" + done + + end=$(date +%s%3N) + echo "Duration: $((end - start)) ms" +} + +test_rename() { + test_rename_one + test_rename_nested +} + +_umount "$MNT_DIR" + +test_copy +test_rename + +mount "$MNT_DIR" $DEF_MNT_PARAMS + diff --git a/test/run-cases.sh b/test/run-cases.sh index 68a3f3a..358e1e0 100644 --- a/test/run-cases.sh +++ b/test/run-cases.sh @@ -10,7 +10,14 @@ export ENDPOINT=${ENDPOINT:-"http://localhost:8080"} . "$(dirname "$0")/mount.sh" -_s3cmd mb s3://$BUCKET_NAME +if [ "$NO_PROXY" == "" ]; then + . "$(dirname "$0")/run-proxy.sh" +fi + +sleep 5 + +#_s3cmd mb s3://$BUCKET_NAME +AWS_ENDPOINT_URL=$ENDPOINT aws s3 mb s3://$BUCKET_NAME export DEF_MNT_PARAMS="--enable-mtime --enable-specials --enable-perms" # shellcheck disable=SC2086 diff --git a/test/run-proxy.sh b/test/run-proxy.sh index 80ebd44..43fe144 100755 --- a/test/run-proxy.sh +++ b/test/run-proxy.sh @@ -57,6 +57,10 @@ if [ "$PROXY_BIN" != "" ]; then $PROXY_BIN & PROXY_PID=$! export EMULATOR=1 + until curl -s "$ENDPOINT" > /dev/null; do + echo "Waiting for proxy up..." + sleep 1 + done elif [ "$TIMEOUT" == "10m" ]; then # higher timeout for testing to real cloud TIMEOUT=45m