Repository: juicedata/juicefs
Pull request #2270 (2022-06-18 06:47:30)

CI: add sync empty dir in gateway test.


Patch:
diff --git a/.github/actions/gateway/action.yml b/.github/actions/gateway/action.yml
index 9a93668d..180424af 100644
--- a/.github/actions/gateway/action.yml
+++ b/.github/actions/gateway/action.yml
@@ -43,8 +43,8 @@ runs:
         #if it is self ruuner
         hostname | grep bench && export GOPATH=/usr/local/go && export HOME=/root
         make juicefs 
-        # wget -q https://github.com/juicedata/juicefs/releases/download/v1.0.0-beta3/juicefs-1.0.0-beta3-linux-amd64.tar.gz
-        # tar -xzf juicefs-1.0.0-beta3-linux-amd64.tar.gz
+        #wget -q https://github.com/juicedata/juicefs/releases/download/v1.0.0-beta3/juicefs-1.0.0-beta3-linux-amd64.tar.gz
+        #tar -xzf juicefs-1.0.0-beta3-linux-amd64.tar.gz
       shell: bash
 
     - name: Install tools
@@ -54,7 +54,9 @@ runs:
       shell: bash
 
     - name: Init
+      shell: bash
       run: | 
+        set -x
         mp=${{inputs.mount_point}}
         meta_url=${{inputs.meta_url}}
         volume=${{inputs.volume_name}}
@@ -65,19 +67,12 @@ runs:
         fi
         test -d /var/jfs/$volume && rm -rf /var/jfs/$volume || true
         echo finished
+      
+    - name: create database
       shell: bash
-
-    - name: Test
-      run: |
+      run: | 
         meta_url=${{inputs.meta_url}}
-        file_size=${{inputs.file_size}}
-        file_count=${{inputs.file_count}}
-        threads=${{inputs.threads}}
         isolation_level="${{inputs.isolation_level}}"
-        mp=${{inputs.mount_point}}
-        volume=${{inputs.volume_name}}
-        echo meta_url is: $meta_url, file_size is $file_size, file_count is $file_count, isolation_level is $isolation_level
-        
         db_name=$(basename $meta_url | awk -F? '{print $1}')
         if [[ "$meta_url" == mysql* ]]; then
           user=$(echo $meta_url |  awk -F/ '{print $3}' | awk -F@ '{print $1}' | awk -F: '{print $1}')
@@ -93,13 +88,19 @@ runs:
           export PGPASSWORD="postgres"
           printf "\set AUTOCOMMIT on\ndrop database if exists $db_name; create database $db_name; " |  psql -U postgres -h localhost
           printf "\set AUTOCOMMIT on\nALTER DATABASE $db_name SET DEFAULT_TRANSACTION_ISOLATION TO '$isolation_level';" |  psql -U postgres -h localhost
-        fi
-        
+        fi      
+      
+    - name: start gateway
+      shell: bash
+      run: |
+        meta_url=${{inputs.meta_url}}
+        mp=${{inputs.mount_point}}
+        volume=${{inputs.volume_name}}
         export MINIO_ROOT_USER=minioadmin
         export MINIO_ROOT_PASSWORD=minioadmin
         ./juicefs format $meta_url $volume --trash-days 0
         if [[ "$meta_url" == badger* ]]; then
-          ./juicefs gateway $meta_url localhost:8080 --no-usage-report &
+          ./juicefs gateway $meta_url localhost:8080 --no-usage-report /tmp/access1.log &
         else
           ./juicefs gateway $meta_url localhost:8080 --access-log /tmp/access1.log &
           #./juicefs gateway $meta_url localhost:9000 --access-log /tmp/access1.log &
@@ -108,13 +109,24 @@ runs:
           #sudo rm /etc/nginx/sites-enabled/default
           #sudo systemctl restart nginx
         fi
+      
+    - name: Sync with multiple process
+      shell: bash
+      run: |
+        meta_url=${{inputs.meta_url}}
+        file_size=${{inputs.file_size}}
+        file_count=${{inputs.file_count}}
+        threads=${{inputs.threads}}
+        mp=${{inputs.mount_point}}
+        volume=${{inputs.volume_name}}
+        
         dd if=/dev/urandom of=file iflag=fullblock,count_bytes bs=4k count="$file_size" > /dev/null
         mkdir data
         for i in $(seq 1 $file_count); do
           cp file data/file$i
         done
         start=`date +%s`
-        declare -a pids        
+        declare -a pids   
         ./juicefs sync --dirs data/  s3://minioadmin:minioadmin@localhost:8080/$volume/data/ --no-https -p $threads &
         pids+=($!)
         ./juicefs sync --dirs data/  s3://minioadmin:minioadmin@localhost:8080/$volume/data/ --no-https -p $threads &
@@ -122,8 +134,6 @@ runs:
         ./juicefs sync --dirs data/  s3://minioadmin:minioadmin@localhost:8080/$volume/data/ --no-https -p $threads &
         pids+=($!)
         wait "${pids[@]}"
-        # mkdir emptydir
-        # ./juicefs sync emptydir/ s3://minioadmin:minioadmin@localhost:8080/$volume/ --delete-dst --no-https -p $threads
         rm -rf $HOME/.juicefs/cache/ || true
         # ./mc alias set minio http://localhost:9000 minioadmin minioadmin --api S3v4
         # ./mc mb minio/$volume
@@ -131,11 +141,36 @@ runs:
         end=`date +%s`
         time=$((end-start))
         echo time cost is: $time second
-        # stop juicefs gateway
         killall juicefs 
         sleep 3
         ./juicefs mount -d $meta_url $mp
         diff -ur data/ $mp/data/ 
         echo "diff succeed"
-        rm $mp/data/ -rf
+        ./juicefs umount  $mp --force
+      
+    - name: Sync with empty dir
+      run: |
+        meta_url=${{inputs.meta_url}}
+        threads=${{inputs.threads}}
+        mp=${{inputs.mount_point}}
+        volume=${{inputs.volume_name}}
+        export MINIO_ROOT_USER=minioadmin
+        export MINIO_ROOT_PASSWORD=minioadmin
+        ./juicefs mdtest $meta_url test --dirs 10 --depth 2 --files 10 --threads 10 --no-usage-report
+        ./juicefs gateway $meta_url localhost:8080 --access-log /tmp/access1.log &
+        sleep 10
+        mkdir emptydir
+        declare -a pids  
+        ./juicefs sync emptydir/ s3://minioadmin:minioadmin@localhost:8080/$volume/test/ --delete-dst --no-https -p $threads &
+        pids+=($!)
+        ./juicefs sync emptydir/ s3://minioadmin:minioadmin@localhost:8080/$volume/test/ --delete-dst --no-https -p $threads &
+        pids+=($!)
+        ./juicefs sync emptydir/ s3://minioadmin:minioadmin@localhost:8080/$volume/test/ --delete-dst --no-https -p $threads &
+        pids+=($!)
+        wait "${pids[@]}"
+        killall juicefs
+        sleep 3
+        ./juicefs mount -d $meta_url $mp
+        [ -d "$mp/test/" ] && exit 1 
+        ./juicefs umount  $mp --force
       shell: bash
\ No newline at end of file
diff --git a/.github/actions/vdbench/action.yml b/.github/actions/vdbench/action.yml
new file mode 100644
index 00000000..c1d74e9f
--- /dev/null
+++ b/.github/actions/vdbench/action.yml
@@ -0,0 +1,102 @@
+name: 'Vdbench Action'
+description: 'Vdbench Action'
+inputs:
+  meta_url:  
+    description: 'meta url'
+    required: true
+    default: 'redis://localhost/1'
+  storage:  
+    description: 'storage'
+    required: true
+    default: 'minio'
+  bucket:  
+    description: 'bucket'
+    required: true
+    default: 'http://localhost:9000/vdbench-test'
+  access_key:  
+    description: 'access_key'
+    required: true
+    default: 'minioadmin'
+  secret_key:  
+    description: 'secret_key'
+    required: true
+    default: 'minioadmin'
+  volume_name: 
+    description: 'volume name'
+    required: true
+    default: 'vdbench-test'
+  mount_point: 
+    description: 'mount_point'
+    required: true
+    default: '/tmp/vdbench'
+  version_count:
+    description: 'how many version to test besides dev'
+    required: true
+    default: 1
+  mysql_password:
+    description: 'mysql password'
+    required: true
+    default: ''
+  PYROSCOPE_AUTH_TOKEN:
+    description: 'PYROSCOPE_AUTH_TOKEN'
+    required: true
+    default: ''
+
+runs:
+  using: "composite"
+  steps:
+    - name: Install tools
+      shell: bash
+      run: |
+        wget -q https://dl.minio.io/client/mc/release/linux-amd64/mc
+        chmod +x mc 
+        wget -q https://s.juicefs.com/static/bench/vdbench50407.zip
+        unzip vdbench50407.zip -d vdbench
+        [[ ${{ inputs.meta_url }} == redis://* ]] && docker run --name my-redis -p 6379:6379 -d redis
+        [[ ${{ inputs.storage }} == minio ]] && docker run -d -p 9000:9000 --name minio \
+                     -e "MINIO_ACCESS_KEY=minioadmin" \
+                     -e "MINIO_SECRET_KEY=minioadmin" \
+                     -v /tmp/data:/data \
+                     -v /tmp/config:/root/.minio \
+                     minio/minio server /data
+      
+    - name: Build linux target
+      shell: bash
+      run: |
+        make juicefs
+      
+    - name: vdbench-big 
+      shell: bash
+      run: | 
+        ./juicefs format ${{inputs.meta_url}} vdbench-big
+        ./juicefs mount -d ${{inputs.meta_url}} ${{ inputs.mount_point }}
+        vdbench/vdbench -f ./.github/workflows/resources/vdbench_big_file.conf
+
+    - uses: actions/upload-artifact@v3
+      with:
+        name: output-big
+        path: output
+
+    - name: clean
+      shell: bash
+      run: |
+        rm /var/jfsCache/ -rf || true
+        if [ -d ${{ inputs.mount_point }} ]; then
+          ./juicefs umount ${{ inputs.mount_point }} || true
+        fi
+        uuid=$(./juicefs status ${{ inputs.meta_url }} | grep UUID | cut -d '"' -f 4) || true
+        if [ -n "$uuid" ];then
+          sudo ./juicefs destroy --force ${{ inputs.meta_url }} $uuid
+        fi
+
+    - name: vdbench-small
+      shell: bash
+      run: |
+        ./juicefs format ${{inputs.meta_url}} vdbench-small
+        ./juicefs mount -d ${{inputs.meta_url}} ${{ inputs.mount_point }}        
+        vdbench/vdbench -f ./.github/workflows/resources/vdbench_small_file.conf
+
+    - uses: actions/upload-artifact@v3
+      with:
+        name: output-small
+        path: output
diff --git a/.github/workflows/resources/vdbench_big_file.conf b/.github/workflows/resources/vdbench_big_file.conf
new file mode 100644
index 00000000..86267c45
--- /dev/null
+++ b/.github/workflows/resources/vdbench_big_file.conf
@@ -0,0 +1,7 @@
+fsd=fsd1,anchor=/tmp/vdbench/vdbench-big,depth=1,width=1,files=4,size=1g,openflags=o_direct
+
+fwd=fwd1,fsd=fsd1,operation=write,xfersize=1m,fileio=sequential,fileselect=sequential,threads=4
+fwd=fwd2,fsd=fsd1,operation=read,xfersize=1m,fileio=sequential,fileselect=sequential,threads=4
+
+rd=rd1,fwd=fwd1,fwdrate=max,format=restart,elapsed=10,interval=1
+rd=rd2,fwd=fwd2,fwdrate=max,format=restart,elapsed=10,interval=1
\ No newline at end of file
diff --git a/.github/workflows/resources/vdbench_small_file.conf b/.github/workflows/resources/vdbench_small_file.conf
new file mode 100644
index 00000000..55411292
--- /dev/null
+++ b/.github/workflows/resources/vdbench_small_file.conf
@@ -0,0 +1,9 @@
+fsd=fsd1,anchor=/tmp/vdbench/vdbench-small,depth=4,width=10,files=10,size=128k,openflags=o_direct
+
+fwd=fwd1,fsd=fsd1,operation=write,xfersize=128k,fileio=random,fileselect=random,threads=4
+fwd=fwd2,fsd=fsd1,operation=read,xfersize=128k,fileio=random,fileselect=random,threads=4
+fwd=fwd3,fsd=fsd1,rdpct=70,xfersize=128k,fileio=random,fileselect=random,threads=4
+
+rd=rd1,fwd=fwd1,fwdrate=max,format=restart,elapsed=60,interval=1
+rd=rd2,fwd=fwd2,fwdrate=max,format=restart,elapsed=60,interval=1
+rd=rd3,fwd=fwd3,fwdrate=max,format=restart,elapsed=60,interval=1
\ No newline at end of file
diff --git a/.github/workflows/vdbench.yml b/.github/workflows/vdbench.yml
new file mode 100644
index 00000000..21175e35
--- /dev/null
+++ b/.github/workflows/vdbench.yml
@@ -0,0 +1,44 @@
+name: "vdbench"
+
+on:
+  push:
+    branches:
+      - 'main'
+      - 'release-**'
+    paths-ignore:
+      - 'docs/**'
+  pull_request:
+    #The branches below must be a subset of the branches above
+    branches:
+      - 'main'
+      - 'release-**'
+    paths-ignore:
+      - 'docs/**'
+  schedule:
+    - cron: '0 0 * * *'
+  workflow_dispatch:
+  
+jobs:
+  vdbench:
+    runs-on: ubuntu-latest
+    steps:        
+      - name: Checkout
+        uses: actions/checkout@v3
+        with:
+          fetch-depth: 1
+          
+      - name: Test
+        uses: ./.github/actions/vdbench
+        with:
+          meta_url: "redis://localhost/1"
+          
+      - name: Send Slack Notification
+        if: ${{ failure() }}
+        uses: juicedata/slack-notify-action@main
+        with:
+          channel-id: "${{ secrets.SLACK_CHANNEL_ID_FOR_PR_CHECK_NOTIFY }}"
+          slack_bot_token: "${{ secrets.SLACK_BOT_TOKEN }}"          
+
+      - name: Setup upterm session
+        if: ${{ failure() }}
+        uses: lhotari/action-upterm@v1
diff --git a/cmd/config.go b/cmd/config.go
index ee2a102c..13941d09 100644
--- a/cmd/config.go
+++ b/cmd/config.go
@@ -71,6 +71,10 @@ $ juicefs config redis://localhost --min-client-version 1.0.0 --max-client-versi
 				Name:  "secret-key",
 				Usage: "secret key for object storage",
 			},
+			&cli.StringFlag{
+				Name:  "session-token",
+				Usage: "session token for object storage",
+			},
 			&cli.BoolFlag{
 				Name:  "encrypt-secret",
 				Usage: "encrypt the secret key if it was previously stored in plain format",
@@ -129,8 +133,8 @@ func config(ctx *cli.Context) error {
 	}
 
 	var quota, storage, trash, clientVer bool
-	var encrypted bool
 	var msg strings.Builder
+	encrypted := format.KeyEncrypted
 	for _, flag := range ctx.LocalFlagNames() {
 		switch flag {
 		case "capacity":
@@ -166,9 +170,17 @@ func config(ctx *cli.Context) error {
 			}
 		case "secret-key": // always update
 			msg.WriteString(fmt.Sprintf("%10s: updated\n", flag))
-			encrypted = format.KeyEncrypted
+			if err := format.Decrypt(); err != nil && strings.Contains(err.Error(), "secret was removed") {
+				logger.Warnf("decrypt secrets: %s", err)
+			}
 			format.SecretKey = ctx.String(flag)
-			format.KeyEncrypted = false
+			storage = true
+		case "session-token": // always update
+			msg.WriteString(fmt.Sprintf("%10s: updated\n", flag))
+			if err := format.Decrypt(); err != nil && strings.Contains(err.Error(), "secret was removed") {
+				logger.Warnf("decrypt secrets: %s", err)
+			}
+			format.SessionToken = ctx.String(flag)
 			storage = true
 		case "trash-days":
 			if new := ctx.Int(flag); new != format.TrashDays {
diff --git a/cmd/format.go b/cmd/format.go
index c9e1be66..16692bbd 100644
--- a/cmd/format.go
+++ b/cmd/format.go
@@ -139,6 +139,10 @@ Details: https://juicefs.com/docs/community/quick_start_guide`,
 				Name:  "secret-key",
 				Usage: "secret key for object storage (env SECRET_KEY)",
 			},
+			&cli.StringFlag{
+				Name:  "session-token",
+				Usage: "session token for object storage",
+			},
 			&cli.StringFlag{
 				Name:  "encrypt-rsa-key",
 				Usage: "a path to RSA private key (PEM)",
@@ -207,9 +211,9 @@ func createStorage(format meta.Format) (object.ObjectStorage, error) {
 		object.GetHttpClient().Transport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: tlsSkipVerify}
 	}
 	if format.Shards > 1 {
-		blob, err = object.NewSharded(strings.ToLower(format.Storage), format.Bucket, format.AccessKey, format.SecretKey, format.Shards)
+		blob, err = object.NewSharded(strings.ToLower(format.Storage), format.Bucket, format.AccessKey, format.SecretKey, format.SessionToken, format.Shards)
 	} else {
-		blob, err = object.CreateStorage(strings.ToLower(format.Storage), format.Bucket, format.AccessKey, format.SecretKey)
+		blob, err = object.CreateStorage(strings.ToLower(format.Storage), format.Bucket, format.AccessKey, format.SecretKey, format.SessionToken)
 	}
 	if err != nil {
 		return nil, err
@@ -353,8 +357,16 @@ func format(c *cli.Context) error {
 				format.AccessKey = c.String(flag)
 			case "secret-key":
 				encrypted = format.KeyEncrypted
+				if err := format.Decrypt(); err != nil && strings.Contains(err.Error(), "secret was removed") {
+					logger.Warnf("decrypt secrets: %s", err)
+				}
 				format.SecretKey = c.String(flag)
-				format.KeyEncrypted = false
+			case "session-key":
+				encrypted = format.KeyEncrypted
+				if err := format.Decrypt(); err != nil && strings.Contains(err.Error(), "secret was removed") {
+					logger.Warnf("decrypt secrets: %s", err)
+				}
+				format.SessionToken = c.String(flag)
 			case "trash-days":
 				format.TrashDays = c.Int(flag)
 			case "block-size":
@@ -374,21 +386,22 @@ func format(c *cli.Context) error {
 	} else if err.Error() == "database is not formatted" {
 		create = true
 		format = &meta.Format{
-			Name:        name,
-			UUID:        uuid.New().String(),
-			Storage:     c.String("storage"),
-			Bucket:      c.String("bucket"),
-			AccessKey:   c.String("access-key"),
-			SecretKey:   c.String("secret-key"),
-			EncryptKey:  loadEncrypt(c.String("encrypt-rsa-key")),
-			Shards:      c.Int("shards"),
-			HashPrefix:  c.Bool("hash-prefix"),
-			Capacity:    c.Uint64("capacity") << 30,
-			Inodes:      c.Uint64("inodes"),
-			BlockSize:   fixObjectSize(c.Int("block-size")),
-			Compression: c.String("compress"),
-			TrashDays:   c.Int("trash-days"),
-			MetaVersion: 1,
+			Name:         name,
+			UUID:         uuid.New().String(),
+			Storage:      c.String("storage"),
+			Bucket:       c.String("bucket"),
+			AccessKey:    c.String("access-key"),
+			SecretKey:    c.String("secret-key"),
+			SessionToken: c.String("session-token"),
+			EncryptKey:   loadEncrypt(c.String("encrypt-rsa-key")),
+			Shards:       c.Int("shards"),
+			HashPrefix:   c.Bool("hash-prefix"),
+			Capacity:     c.Uint64("capacity") << 30,
+			Inodes:       c.Uint64("inodes"),
+			BlockSize:    fixObjectSize(c.Int("block-size")),
+			Compression:  c.String("compress"),
+			TrashDays:    c.Int("trash-days"),
+			MetaVersion:  1,
 		}
 		if format.AccessKey == "" && os.Getenv("ACCESS_KEY") != "" {
 			format.AccessKey = os.Getenv("ACCESS_KEY")
@@ -398,6 +411,10 @@ func format(c *cli.Context) error {
 			format.SecretKey = os.Getenv("SECRET_KEY")
 			_ = os.Unsetenv("SECRET_KEY")
 		}
+		if format.SessionToken == "" && os.Getenv("SESSION_TOKEN") != "" {
+			format.SessionToken = os.Getenv("SESSION_TOKEN")
+			_ = os.Unsetenv("SESSION_TOKEN")
+		}
 	} else {
 		logger.Fatalf("Load metadata: %s", err)
 	}
diff --git a/cmd/mount.go b/cmd/mount.go
index f11179f4..ab4176b8 100644
--- a/cmd/mount.go
+++ b/cmd/mount.go
@@ -377,7 +377,7 @@ func NewReloadableStorage(format *meta.Format, reload func() (*meta.Format, erro
 				logger.Warnf("reload config: %s", err)
 				continue
 			}
-			if new.Storage != old.Storage || new.Bucket != old.Bucket || new.AccessKey != old.AccessKey || new.SecretKey != old.SecretKey {
+			if new.Storage != old.Storage || new.Bucket != old.Bucket || new.AccessKey != old.AccessKey || new.SecretKey != old.SecretKey || new.SessionToken != old.SessionToken {
 				logger.Infof("found new configuration: storage=%s bucket=%s ak=%s", new.Storage, new.Bucket, new.AccessKey)
 				newBlob, err := createStorage(*new)
 				if err != nil {
diff --git a/cmd/objbench.go b/cmd/objbench.go
index 26231904..1bb675e5 100644
--- a/cmd/objbench.go
+++ b/cmd/objbench.go
@@ -66,6 +66,10 @@ Details: https://juicefs.com/docs/community/performance_evaluation_guide#juicefs
 				Name:  "secret-key",
 				Usage: "secret key for object storage (env SECRET_KEY)",
 			},
+			&cli.StringFlag{
+				Name:  "session-token",
+				Usage: "session token for object storage",
+			},
 			&cli.UintFlag{
 				Name:  "block-size",
 				Value: 4096,
@@ -114,14 +118,17 @@ func objbench(ctx *cli.Context) error {
 			logger.Fatalf("%s should not be set to zero", name)
 		}
 	}
-	ak, sk := ctx.String("access-key"), ctx.String("secret-key")
+	ak, sk, token := ctx.String("access-key"), ctx.String("secret-key"), ctx.String("session-token")
 	if ak == "" {
 		ak = os.Getenv("ACCESS_KEY")
 	}
 	if sk == "" {
 		sk = os.Getenv("SECRET_KEY")
 	}
-	blobOrigin, err := object.CreateStorage(strings.ToLower(ctx.String("storage")), ctx.Args().First(), ak, sk)
+	if token == "" {
+		token = os.Getenv("SESSION_TOKEN")
+	}
+	blobOrigin, err := object.CreateStorage(strings.ToLower(ctx.String("storage")), ctx.Args().First(), ak, sk, token)
 	if err != nil {
 		logger.Fatalf("create storage failed: %v", err)
 	}
diff --git a/cmd/sync.go b/cmd/sync.go
index 64ee9206..930d8f25 100644
--- a/cmd/sync.go
+++ b/cmd/sync.go
@@ -232,7 +232,7 @@ func createSyncStorage(uri string, conf *sync.Config) (object.ObjectStorage, err
 				user = parts[0]
 				pass = parts[1]
 			}
-			return object.CreateStorage("sftp", uri, user, pass)
+			return object.CreateStorage("sftp", uri, user, pass, "")
 		}
 	}
 	u, err := url.Parse(uri)
@@ -267,7 +267,7 @@ func createSyncStorage(uri string, conf *sync.Config) (object.ObjectStorage, err
 		endpoint += u.Path
 	}
 
-	store, err := object.CreateStorage(name, endpoint, accessKey, secretKey)
+	store, err := object.CreateStorage(name, endpoint, accessKey, secretKey, "")
 	if err != nil {
 		return nil, fmt.Errorf("create %s %s: %s", name, endpoint, err)
 	}
diff --git a/docs/en/reference/how_to_setup_object_storage.md b/docs/en/reference/how_to_setup_object_storage.md
index 3f8a25c8..01f20975 100644
--- a/docs/en/reference/how_to_setup_object_storage.md
+++ b/docs/en/reference/how_to_setup_object_storage.md
@@ -217,7 +217,7 @@ As you can see, there is no need to include authentication information in the co
 
 To use Azure Blob Storage as data storage of JuiceFS, please [check the documentation](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage) to learn how to view the storage account name and access key, which correspond to the values ​​of the `--access-key` and `--secret-key` options, respectively.
 
-The `--bucket` option is set in the format `https://<container>.<endpoint>`, please replace `<container>` with the name of the actual blob container and `<endpoint>` with `blob.core.windows.net` (Azure Global) or `blob.core.chinacloudapi.cn` (Azure China). For example:
+The `--bucket` option is set in the format `https://<container>.<endpoint>`, please replace `<container>` with the name of the actual blob container and `<endpoint>` with `core.windows.net` (Azure Global) or `core.chinacloudapi.cn` (Azure China). For example:
 
 ```bash
 juicefs format \
diff --git a/docs/zh_cn/reference/how_to_setup_object_storage.md b/docs/zh_cn/reference/how_to_setup_object_storage.md
index f7901a7f..11aa2f01 100644
--- a/docs/zh_cn/reference/how_to_setup_object_storage.md
+++ b/docs/zh_cn/reference/how_to_setup_object_storage.md
@@ -221,7 +221,7 @@ $ juicefs format \
 
 使用 Azure Blob 存储作为 JuiceFS 的数据存储，请先 [查看文档](https://docs.microsoft.com/zh-cn/azure/storage/common/storage-account-keys-manage) 了解如何查看存储帐户的名称和密钥，它们分别对应 `--access-key` 和 `--secret-key` 选项的值。
 
-`--bucket` 选项的设置格式为 `https://<container>.<endpoint>`，请将其中的 `<container>` 替换为实际的 Blob 容器的名称，将 `<endpoint>` 替换为 `blob.core.windows.net`（Azure 全球）或 `blob.core.chinacloudapi.cn`（Azure 中国）。例如：
+`--bucket` 选项的设置格式为 `https://<container>.<endpoint>`，请将其中的 `<container>` 替换为实际的 Blob 容器的名称，将 `<endpoint>` 替换为 `core.windows.net`（Azure 全球）或 `core.chinacloudapi.cn`（Azure 中国）。例如：
 
 ```bash
 juicefs format \
diff --git a/pkg/meta/config.go b/pkg/meta/config.go
index 003e895c..aae04d76 100644
--- a/pkg/meta/config.go
+++ b/pkg/meta/config.go
@@ -50,6 +50,7 @@ type Format struct {
 	Bucket           string
 	AccessKey        string `json:",omitempty"`
 	SecretKey        string `json:",omitempty"`
+	SessionToken     string `json:",omitempty"`
 	BlockSize        int
 	Compression      string `json:",omitempty"`
 	Shards           int    `json:",omitempty"`
@@ -98,6 +99,9 @@ func (f *Format) RemoveSecret() {
 	if f.SecretKey != "" {
 		f.SecretKey = "removed"
 	}
+	if f.SessionToken != "" {
+		f.SessionToken = "removed"
+	}
 	if f.EncryptKey != "" {
 		f.EncryptKey = "removed"
 	}
@@ -137,7 +141,7 @@ func (f *Format) CheckVersion() error {
 }
 
 func (f *Format) Encrypt() error {
-	if f.KeyEncrypted || f.SecretKey == "" && f.EncryptKey == "" {
+	if f.KeyEncrypted || f.SecretKey == "" && f.EncryptKey == "" && f.SessionToken == "" {
 		return nil
 	}
 	key := md5.Sum([]byte(f.UUID))
@@ -149,38 +153,32 @@ func (f *Format) Encrypt() error {
 	if err != nil {
 		return fmt.Errorf("new GCM: %s", err)
 	}
-	nonce := make([]byte, 12)
-	encrypt := func(k string) string {
-		ciphertext := aesgcm.Seal(nil, nonce, []byte(k), nil)
+	encrypt := func(k *string) {
+		if *k == "" {
+			return
+		}
+		nonce := make([]byte, 12)
+		if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
+			logger.Fatalf("generate nonce for secret key: %s", err)
+		}
+		ciphertext := aesgcm.Seal(nil, nonce, []byte(*k), nil)
 		buf := make([]byte, 12+len(ciphertext))
 		copy(buf, nonce)
 		copy(buf[12:], ciphertext)
-		return base64.StdEncoding.EncodeToString(buf)
+		*k = base64.StdEncoding.EncodeToString(buf)
 	}
 
-	if f.SecretKey != "" {
-		if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
-			return fmt.Errorf("generate nonce for secret key: %s", err)
-		}
-		f.SecretKey = encrypt(f.SecretKey)
-	}
-	if f.EncryptKey != "" {
-		if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
-			return fmt.Errorf("generate nonce for encrypt key: %s", err)
-		}
-		f.EncryptKey = encrypt(f.EncryptKey)
-	}
+	encrypt(&f.SecretKey)
+	encrypt(&f.SessionToken)
+	encrypt(&f.EncryptKey)
 	f.KeyEncrypted = true
 	return nil
 }
 
 func (f *Format) Decrypt() error {
-	if !f.KeyEncrypted || f.SecretKey == "" && f.EncryptKey == "" {
+	if !f.KeyEncrypted {
 		return nil
 	}
-	if f.SecretKey == "removed" {
-		return fmt.Errorf("secret key was removed; please correct it with `config` command")
-	}
 	key := md5.Sum([]byte(f.UUID))
 	block, err := aes.NewCipher(key[:])
 	if err != nil {
@@ -190,29 +188,30 @@ func (f *Format) Decrypt() error {
 	if err != nil {
 		return fmt.Errorf("new GCM: %s", err)
 	}
-	decrypt := func(k *string) error {
-		buf, err := base64.StdEncoding.DecodeString(*k)
-		if err != nil {
-			return fmt.Errorf("decode key: %s", err)
+	decrypt := func(k *string) {
+		if *k == "" {
+			return
 		}
-		plaintext, err := aesgcm.Open(nil, buf[:12], buf[12:], nil)
-		if err != nil {
-			return fmt.Errorf("open cipher: %s", err)
+		if *k == "removed" {
+			err = fmt.Errorf("secret was removed; please correct it with `config` command")
+			return
 		}
-		*k = string(plaintext)
-		return nil
-	}
-
-	if f.SecretKey != "" {
-		if err = decrypt(&f.SecretKey); err != nil {
-			return err
+		buf, e := base64.StdEncoding.DecodeString(*k)
+		if e != nil {
+			err = fmt.Errorf("decode key: %s", e)
+			return
 		}
-	}
-	if f.EncryptKey != "" {
-		if err = decrypt(&f.EncryptKey); err != nil {
-			return err
+		plaintext, e := aesgcm.Open(nil, buf[:12], buf[12:], nil)
+		if e != nil {
+			err = fmt.Errorf("open cipher: %s", e)
+			return
 		}
+		*k = string(plaintext)
 	}
+
+	decrypt(&f.EncryptKey)
+	decrypt(&f.SecretKey)
+	decrypt(&f.SessionToken)
 	f.KeyEncrypted = false
-	return nil
+	return err
 }
diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go
index 1743ff45..e780fac4 100644
--- a/pkg/meta/redis.go
+++ b/pkg/meta/redis.go
@@ -3191,6 +3191,10 @@ func (m *redisMeta) DumpMeta(w io.Writer, root Ino) (err error) {
 		dm.Setting.SecretKey = "removed"
 		logger.Warnf("Secret key is removed for the sake of safety")
 	}
+	if dm.Setting.SessionToken != "" {
+		dm.Setting.SessionToken = "removed"
+		logger.Warnf("Session token is removed for the sake of safety")
+	}
 	bw, err := dm.writeJsonWithOutTree(w)
 	if err != nil {
 		return err
diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go
index 2cccad20..733259fc 100644
--- a/pkg/meta/sql.go
+++ b/pkg/meta/sql.go
@@ -2938,6 +2938,10 @@ func (m *dbMeta) DumpMeta(w io.Writer, root Ino) (err error) {
 			dm.Setting.SecretKey = "removed"
 			logger.Warnf("Secret key is removed for the sake of safety")
 		}
+		if dm.Setting.SessionToken != "" {
+			dm.Setting.SessionToken = "removed"
+			logger.Warnf("Session token is removed for the sake of safety")
+		}
 		bw, err := dm.writeJsonWithOutTree(w)
 		if err != nil {
 			return err
diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go
index 034a937f..dc7b638c 100644
--- a/pkg/meta/tkv.go
+++ b/pkg/meta/tkv.go
@@ -2526,6 +2526,10 @@ func (m *kvMeta) DumpMeta(w io.Writer, root Ino) (err error) {
 		dm.Setting.SecretKey = "removed"
 		logger.Warnf("Secret key is removed for the sake of safety")
 	}
+	if dm.Setting.SessionToken != "" {
+		dm.Setting.SessionToken = "removed"
+		logger.Warnf("Session token is removed for the sake of safety")
+	}
 	bw, err := dm.writeJsonWithOutTree(w)
 	if err != nil {
 		return err
diff --git a/pkg/object/azure.go b/pkg/object/azure.go
index 774df1f1..d5f47486 100644
--- a/pkg/object/azure.go
+++ b/pkg/object/azure.go
@@ -104,12 +104,18 @@ func (b *wasb) List(prefix, marker string, limit int64) ([]Object, error) {
 
 	limit32 := int32(limit)
 	pager := b.container.ListBlobsFlat(&azblob.ContainerListBlobFlatSegmentOptions{Prefix: &prefix, Marker: &marker, Maxresults: &(limit32)})
+	if pager.Err() != nil {
+		return nil, pager.Err()
+	}
 	if pager.NextPage(ctx) {
 		b.marker = *pager.PageResponse().NextMarker
 	} else {
 		b.marker = ""
 	}
-	n := len(pager.PageResponse().Segment.BlobItems)
+	var n int
+	if pager.PageResponse().Segment != nil {
+		n = len(pager.PageResponse().Segment.BlobItems)
+	}
 	objs := make([]Object, n)
 	for i := 0; i < n; i++ {
 		blob := pager.PageResponse().Segment.BlobItems[i]
@@ -150,7 +156,7 @@ func autoWasbEndpoint(containerName, accountName, scheme string, credential *azb
 	return endpoint, nil
 }
 
-func newWabs(endpoint, accountName, accountKey string) (ObjectStorage, error) {
+func newWabs(endpoint, accountName, accountKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
@@ -159,51 +165,31 @@ func newWabs(endpoint, accountName, accountKey string) (ObjectStorage, error) {
 		return nil, fmt.Errorf("Invalid endpoint: %v, error: %v", endpoint, err)
 	}
 	hostParts := strings.SplitN(uri.Host, ".", 2)
-
-	scheme := ""
-	domain := ""
+	containerName := hostParts[0]
 	// Connection string support: DefaultEndpointsProtocol=[http|https];AccountName=***;AccountKey=***;EndpointSuffix=[core.windows.net|core.chinacloudapi.cn]
 	if connString := os.Getenv("AZURE_STORAGE_CONNECTION_STRING"); connString != "" {
-		items := strings.Split(connString, ";")
-		for _, item := range items {
-			if item = strings.TrimSpace(item); item == "" {
-				continue
-			}
-			parts := strings.SplitN(item, "=", 2)
-			if len(parts) != 2 {
-				return nil, fmt.Errorf("Invalid connection string item: %s", item)
-			}
-			// Arguments from command line take precedence
-			if parts[0] == "DefaultEndpointsProtocol" && scheme == "" {
-				scheme = parts[1]
-			} else if parts[0] == "AccountName" && accountName == "" {
-				accountName = parts[1]
-			} else if parts[0] == "AccountKey" && accountKey == "" {
-				accountKey = parts[1]
-			} else if parts[0] == "EndpointSuffix" && domain == "" {
-				domain = parts[1]
-			}
+		var client azblob.ContainerClient
+		if client, err = azblob.NewContainerClientFromConnectionString(connString, containerName, nil); err != nil {
+			return nil, err
 		}
+		return &wasb{container: &client, cName: containerName}, nil
 	}
+
 	credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
 	if err != nil {
 		return nil, err
 	}
-
-	if scheme == "" {
-		scheme = "https"
-	}
-	containerName := hostParts[0]
+	var domain string
 	if len(hostParts) > 1 {
-		// Arguments from command line take precedence
 		domain = hostParts[1]
-	} else if domain == "" {
-		if domain, err = autoWasbEndpoint(containerName, accountName, scheme, credential); err != nil {
-			return nil, fmt.Errorf("Unable to get endpoint of container %s: %s", containerName, err)
+		if !strings.HasPrefix(hostParts[1], "blob") {
+			domain = fmt.Sprintf("blob.%s", hostParts[1])
 		}
+	} else if domain, err = autoWasbEndpoint(containerName, accountName, uri.Scheme, credential); err != nil {
+		return nil, fmt.Errorf("Unable to get endpoint of container %s: %s", containerName, err)
 	}
 
-	client, err := azblob.NewContainerClientWithSharedKey(fmt.Sprintf("%s://%s.%s/%s", scheme, accountName, domain, containerName), credential, nil)
+	client, err := azblob.NewContainerClientWithSharedKey(fmt.Sprintf("%s://%s.%s/%s", uri.Scheme, accountName, domain, containerName), credential, nil)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/object/b2.go b/pkg/object/b2.go
index 954f1538..63bf95be 100644
--- a/pkg/object/b2.go
+++ b/pkg/object/b2.go
@@ -144,7 +144,7 @@ func (c *b2client) List(prefix, marker string, limit int64) ([]Object, error) {
 
 // TODO: support multipart upload using S3 client
 
-func newB2(endpoint, keyID, applicationKey string) (ObjectStorage, error) {
+func newB2(endpoint, keyID, applicationKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
diff --git a/pkg/object/bos.go b/pkg/object/bos.go
index 65f24ee4..299c52c3 100644
--- a/pkg/object/bos.go
+++ b/pkg/object/bos.go
@@ -199,7 +199,7 @@ func autoBOSEndpoint(bucketName, accessKey, secretKey string) (string, error) {
 	}
 }
 
-func newBOS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newBOS(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
diff --git a/pkg/object/cos.go b/pkg/object/cos.go
index 8a01022d..2557286b 100644
--- a/pkg/object/cos.go
+++ b/pkg/object/cos.go
@@ -192,11 +192,12 @@ func (c *COS) ListUploads(marker string) ([]*PendingPart, string, error) {
 	return parts, result.NextKeyMarker, nil
 }
 
-func autoCOSEndpoint(bucketName, accessKey, secretKey string) (string, error) {
+func autoCOSEndpoint(bucketName, accessKey, secretKey, token string) (string, error) {
 	client := cos.NewClient(nil, &http.Client{
 		Transport: &cos.AuthorizationTransport{
-			SecretID:  accessKey,
-			SecretKey: secretKey,
+			SecretID:     accessKey,
+			SecretKey:    secretKey,
+			SessionToken: token,
 		},
 	})
 	client.UserAgent = UserAgent
@@ -215,7 +216,7 @@ func autoCOSEndpoint(bucketName, accessKey, secretKey string) (string, error) {
 	return "", fmt.Errorf("bucket %q doesnot exist", bucketName)
 }
 
-func newCOS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newCOS(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
@@ -231,7 +232,7 @@ func newCOS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 	}
 
 	if len(hostParts) == 1 {
-		if endpoint, err = autoCOSEndpoint(hostParts[0], accessKey, secretKey); err != nil {
+		if endpoint, err = autoCOSEndpoint(hostParts[0], accessKey, secretKey, token); err != nil {
 			return nil, fmt.Errorf("Unable to get endpoint of bucket %s: %s", hostParts[0], err)
 		}
 		if uri, err = url.ParseRequestURI(endpoint); err != nil {
@@ -243,9 +244,10 @@ func newCOS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 	b := &cos.BaseURL{BucketURL: uri}
 	client := cos.NewClient(b, &http.Client{
 		Transport: &cos.AuthorizationTransport{
-			SecretID:  accessKey,
-			SecretKey: secretKey,
-			Transport: httpClient.Transport,
+			SecretID:     accessKey,
+			SecretKey:    secretKey,
+			SessionToken: token,
+			Transport:    httpClient.Transport,
 		},
 	})
 	client.UserAgent = UserAgent
diff --git a/pkg/object/eos.go b/pkg/object/eos.go
index c6cc2cf9..dc08afee 100644
--- a/pkg/object/eos.go
+++ b/pkg/object/eos.go
@@ -39,7 +39,7 @@ func (s *eos) String() string {
 	return fmt.Sprintf("eos://%s/", s.s3client.bucket)
 }
 
-func newEos(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newEos(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
@@ -59,6 +59,9 @@ func newEos(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 	if secretKey == "" {
 		secretKey = os.Getenv("EOS_SECRET_KEY")
 	}
+	if token == "" {
+		token = os.Getenv("EOS_TOKEN")
+	}
 
 	awsConfig := &aws.Config{
 		Endpoint:         &endpoint,
@@ -66,7 +69,7 @@ func newEos(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 		DisableSSL:       aws.Bool(!ssl),
 		S3ForcePathStyle: aws.Bool(false),
 		HTTPClient:       httpClient,
-		Credentials:      credentials.NewStaticCredentials(accessKey, secretKey, ""),
+		Credentials:      credentials.NewStaticCredentials(accessKey, secretKey, token),
 	}
 
 	ses, err := session.NewSession(awsConfig)
diff --git a/pkg/object/etcd.go b/pkg/object/etcd.go
index dbabf99e..914a668a 100644
--- a/pkg/object/etcd.go
+++ b/pkg/object/etcd.go
@@ -155,7 +155,7 @@ func buildTlsConfig(u *url.URL) (*tls.Config, error) {
 	return nil, nil
 }
 
-func newEtcd(addr, user, passwd string) (ObjectStorage, error) {
+func newEtcd(addr, user, passwd, token string) (ObjectStorage, error) {
 	if !strings.Contains(addr, "://") {
 		addr = "http://" + addr
 	}
diff --git a/pkg/object/file.go b/pkg/object/file.go
index c5bcce0f..c1272070 100644
--- a/pkg/object/file.go
+++ b/pkg/object/file.go
@@ -406,7 +406,7 @@ func (d *filestore) Chown(path string, owner, group string) error {
 	return os.Chown(p, uid, gid)
 }
 
-func newDisk(root, accesskey, secretkey string) (ObjectStorage, error) {
+func newDisk(root, accesskey, secretkey, token string) (ObjectStorage, error) {
 	// For Windows, the path looks like /C:/a/b/c/
 	if runtime.GOOS == "windows" && strings.HasPrefix(root, "/") {
 		root = root[1:]
diff --git a/pkg/object/gs.go b/pkg/object/gs.go
index d559b4d9..e15997f9 100644
--- a/pkg/object/gs.go
+++ b/pkg/object/gs.go
@@ -157,7 +157,7 @@ func (g *gs) List(prefix, marker string, limit int64) ([]Object, error) {
 	return objs, nil
 }
 
-func newGS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newGS(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("gs://%s", endpoint)
 	}
diff --git a/pkg/object/hdfs.go b/pkg/object/hdfs.go
index d9481885..5fa22838 100644
--- a/pkg/object/hdfs.go
+++ b/pkg/object/hdfs.go
@@ -315,7 +315,7 @@ func (h *hdfsclient) Chown(key string, owner, group string) error {
 	return h.c.Chown(h.path(key), owner, group)
 }
 
-func newHDFS(addr, username, sk string) (ObjectStorage, error) {
+func newHDFS(addr, username, sk, token string) (ObjectStorage, error) {
 	conf, err := hadoopconf.LoadFromEnvironment()
 	if err != nil {
 		return nil, fmt.Errorf("Problem loading configuration: %s", err)
diff --git a/pkg/object/ibmcos.go b/pkg/object/ibmcos.go
index 48b6ebf0..25cf499a 100644
--- a/pkg/object/ibmcos.go
+++ b/pkg/object/ibmcos.go
@@ -233,7 +233,7 @@ func (s *ibmcos) ListUploads(marker string) ([]*PendingPart, string, error) {
 	return parts, nextMarker, nil
 }
 
-func newIBMCOS(endpoint, apiKey, serviceInstanceID string) (ObjectStorage, error) {
+func newIBMCOS(endpoint, apiKey, serviceInstanceID, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
diff --git a/pkg/object/jss.go b/pkg/object/jss.go
index bb36bc3f..73c1e5b6 100644
--- a/pkg/object/jss.go
+++ b/pkg/object/jss.go
@@ -49,7 +49,7 @@ func (j *jss) Copy(dst, src string) error {
 	return err
 }
 
-func newJSS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newJSS(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
@@ -66,7 +66,7 @@ func newJSS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 		DisableSSL:       aws.Bool(!ssl),
 		S3ForcePathStyle: aws.Bool(true),
 		HTTPClient:       httpClient,
-		Credentials:      credentials.NewStaticCredentials(accessKey, secretKey, ""),
+		Credentials:      credentials.NewStaticCredentials(accessKey, secretKey, token),
 	}
 
 	ses, err := session.NewSession(awsConfig)
diff --git a/pkg/object/ks3.go b/pkg/object/ks3.go
index d9cd11eb..d5378291 100644
--- a/pkg/object/ks3.go
+++ b/pkg/object/ks3.go
@@ -251,7 +251,7 @@ var ks3Regions = map[string]string{
 	"sgp":          "SINGAPORE",
 }
 
-func newKS3(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newKS3(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
@@ -281,7 +281,7 @@ func newKS3(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 		DisableSSL:       !ssl,
 		HTTPClient:       httpClient,
 		S3ForcePathStyle: true,
-		Credentials:      credentials.NewStaticCredentials(accessKey, secretKey, ""),
+		Credentials:      credentials.NewStaticCredentials(accessKey, secretKey, token),
 	}
 
 	return &ks3{bucket, s3.New(awsConfig), nil}, nil
diff --git a/pkg/object/mem.go b/pkg/object/mem.go
index 79d73b0c..eddd04c0 100644
--- a/pkg/object/mem.go
+++ b/pkg/object/mem.go
@@ -165,7 +165,7 @@ func (m *memStore) ListAll(prefix, marker string) (<-chan Object, error) {
 	return nil, notSupported
 }
 
-func newMem(endpoint, accesskey, secretkey string) (ObjectStorage, error) {
+func newMem(endpoint, accesskey, secretkey, token string) (ObjectStorage, error) {
 	store := &memStore{name: endpoint}
 	store.objects = make(map[string]*mobj)
 	return store, nil
diff --git a/pkg/object/minio.go b/pkg/object/minio.go
index 45fd5ed5..fa74c07a 100644
--- a/pkg/object/minio.go
+++ b/pkg/object/minio.go
@@ -39,7 +39,7 @@ func (m *minio) String() string {
 	return fmt.Sprintf("minio://%s/%s/", *m.s3client.ses.Config.Endpoint, m.s3client.bucket)
 }
 
-func newMinio(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newMinio(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("http://%s", endpoint)
 	}
@@ -62,7 +62,7 @@ func newMinio(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 		secretKey = os.Getenv("MINIO_SECRET_KEY")
 	}
 	if accessKey != "" {
-		awsConfig.Credentials = credentials.NewStaticCredentials(accessKey, secretKey, "")
+		awsConfig.Credentials = credentials.NewStaticCredentials(accessKey, secretKey, token)
 	}
 
 	ses, err := session.NewSession(awsConfig)
diff --git a/pkg/object/nos.go b/pkg/object/nos.go
index cb4b30b6..20f0c16c 100644
--- a/pkg/object/nos.go
+++ b/pkg/object/nos.go
@@ -154,7 +154,7 @@ func (s *nos) List(prefix, marker string, limit int64) ([]Object, error) {
 	return objs, nil
 }
 
-func newNOS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newNOS(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
diff --git a/pkg/object/object_storage.go b/pkg/object/object_storage.go
index dd3b270e..137351a9 100644
--- a/pkg/object/object_storage.go
+++ b/pkg/object/object_storage.go
@@ -140,7 +140,7 @@ func (s DefaultObjectStorage) ListAll(prefix, marker string) (<-chan Object, err
 	return nil, notSupported
 }
 
-type Creator func(bucket, accessKey, secretKey string) (ObjectStorage, error)
+type Creator func(bucket, accessKey, secretKey, token string) (ObjectStorage, error)
 
 var storages = make(map[string]Creator)
 
@@ -148,11 +148,11 @@ func Register(name string, register Creator) {
 	storages[name] = register
 }
 
-func CreateStorage(name, endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func CreateStorage(name, endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	f, ok := storages[name]
 	if ok {
 		logger.Debugf("Creating %s storage at endpoint %s", name, endpoint)
-		return f(endpoint, accessKey, secretKey)
+		return f(endpoint, accessKey, secretKey, token)
 	}
 	return nil, fmt.Errorf("invalid storage: %s", name)
 }
diff --git a/pkg/object/obs.go b/pkg/object/obs.go
index 17097690..d341a60d 100644
--- a/pkg/object/obs.go
+++ b/pkg/object/obs.go
@@ -253,14 +253,14 @@ func (s *obsClient) ListUploads(marker string) ([]*PendingPart, string, error) {
 	return parts, nextMarker, nil
 }
 
-func autoOBSEndpoint(bucketName, accessKey, secretKey string) (string, error) {
+func autoOBSEndpoint(bucketName, accessKey, secretKey, token string) (string, error) {
 	region := obsDefaultRegion
 	if r := os.Getenv("HWCLOUD_DEFAULT_REGION"); r != "" {
 		region = r
 	}
 	endpoint := fmt.Sprintf("https://obs.%s.myhuaweicloud.com", region)
 
-	obsCli, err := obs.New(accessKey, secretKey, endpoint)
+	obsCli, err := obs.New(accessKey, secretKey, endpoint, obs.WithSecurityToken(token))
 	if err != nil {
 		return "", err
 	}
@@ -279,7 +279,7 @@ func autoOBSEndpoint(bucketName, accessKey, secretKey string) (string, error) {
 	return "", fmt.Errorf("bucket %q does not exist", bucketName)
 }
 
-func newOBS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newOBS(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
@@ -300,7 +300,7 @@ func newOBS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 
 	var region string
 	if len(hostParts) == 1 {
-		if endpoint, err = autoOBSEndpoint(bucketName, accessKey, secretKey); err != nil {
+		if endpoint, err = autoOBSEndpoint(bucketName, accessKey, secretKey, token); err != nil {
 			return nil, fmt.Errorf("cannot get location of bucket %s: %q", bucketName, err)
 		}
 		if !strings.HasPrefix(endpoint, "http") {
@@ -325,7 +325,8 @@ func newOBS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 
 	// Empty proxy url string has no effect
 	// there is a bug in the retry of PUT (did not call Seek(0,0) before retry), so disable the retry here
-	c, err := obs.New(accessKey, secretKey, endpoint, obs.WithProxyUrl(urlString), obs.WithMaxRetryCount(0))
+	c, err := obs.New(accessKey, secretKey, endpoint, obs.WithSecurityToken(token),
+		obs.WithProxyUrl(urlString), obs.WithMaxRetryCount(0))
 	if err != nil {
 		return nil, fmt.Errorf("fail to initialize OBS: %q", err)
 	}
diff --git a/pkg/object/oos.go b/pkg/object/oos.go
index eb22fbb0..7887823f 100644
--- a/pkg/object/oos.go
+++ b/pkg/object/oos.go
@@ -57,7 +57,7 @@ func (s *oos) List(prefix, marker string, limit int64) ([]Object, error) {
 	return objs, err
 }
 
-func newOOS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newOOS(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
@@ -78,7 +78,7 @@ func newOOS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 		DisableSSL:       aws.Bool(!ssl),
 		S3ForcePathStyle: aws.Bool(!forcePathStyle),
 		HTTPClient:       httpClient,
-		Credentials:      credentials.NewStaticCredentials(accessKey, secretKey, ""),
+		Credentials:      credentials.NewStaticCredentials(accessKey, secretKey, token),
 	}
 
 	ses, err := session.NewSession(awsConfig)
diff --git a/pkg/object/oss.go b/pkg/object/oss.go
index 15aff26a..fcce105e 100644
--- a/pkg/object/oss.go
+++ b/pkg/object/oss.go
@@ -325,7 +325,7 @@ func autoOSSEndpoint(bucketName, accessKey, secretKey, securityToken string) (st
 	return fmt.Sprintf("https://%s.aliyuncs.com", bucketLocation), nil
 }
 
-func newOSS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newOSS(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
@@ -341,12 +341,12 @@ func newOSS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 		domain = uri.Scheme + "://" + hostParts[1]
 	}
 
-	securityToken := ""
+	var refresh bool
 	if accessKey == "" {
 		// try environment variable
 		accessKey = os.Getenv("ALICLOUD_ACCESS_KEY_ID")
 		secretKey = os.Getenv("ALICLOUD_ACCESS_KEY_SECRET")
-		securityToken = os.Getenv("SECURITY_TOKEN")
+		token = os.Getenv("SECURITY_TOKEN")
 
 		if accessKey == "" {
 			if cred, err := fetchStsToken(); err != nil {
@@ -354,24 +354,20 @@ func newOSS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 			} else {
 				accessKey = cred.AccessKeyId
 				secretKey = cred.AccessKeySecret
-				securityToken = cred.SecurityToken
+				token = cred.SecurityToken
+				refresh = true
 			}
 		}
 	}
 
 	if domain == "" {
-		if domain, err = autoOSSEndpoint(bucketName, accessKey, secretKey, securityToken); err != nil {
+		if domain, err = autoOSSEndpoint(bucketName, accessKey, secretKey, token); err != nil {
 			return nil, fmt.Errorf("Unable to get endpoint of bucket %s: %s", bucketName, err)
 		}
 		logger.Debugf("Use endpoint %q", domain)
 	}
 
-	var client *oss.Client
-	if securityToken == "" {
-		client, err = oss.New(domain, accessKey, secretKey)
-	} else {
-		client, err = oss.New(domain, accessKey, secretKey, oss.SecurityToken(securityToken))
-	}
+	client, err := oss.New(domain, accessKey, secretKey, oss.SecurityToken(token))
 	if err != nil {
 		return nil, fmt.Errorf("Cannot create OSS client with endpoint %s: %s", endpoint, err)
 	}
@@ -391,7 +387,7 @@ func newOSS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 	}
 
 	o := &ossClient{client: client, bucket: bucket}
-	if securityToken != "" {
+	if token != "" && refresh {
 		go func() {
 			for {
 				next := o.refreshToken()
diff --git a/pkg/object/qingstor.go b/pkg/object/qingstor.go
index 1da0cb2b..56f2491a 100644
--- a/pkg/object/qingstor.go
+++ b/pkg/object/qingstor.go
@@ -256,7 +256,7 @@ func (q *qingstor) ListUploads(marker string) ([]*PendingPart, string, error) {
 	return parts, nextMarker, nil
 }
 
-func newQingStor(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newQingStor(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
diff --git a/pkg/object/qiniu.go b/pkg/object/qiniu.go
index f7799476..b1076979 100644
--- a/pkg/object/qiniu.go
+++ b/pkg/object/qiniu.go
@@ -166,7 +166,7 @@ func (q *qiniu) List(prefix, marker string, limit int64) ([]Object, error) {
 	return objs, nil
 }
 
-func newQiniu(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newQiniu(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
@@ -189,7 +189,7 @@ func newQiniu(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 		region = endpoint[:strings.LastIndex(endpoint, "-")]
 	}
 	awsConfig := &aws.Config{
-		Credentials:      credentials.NewStaticCredentials(accessKey, secretKey, ""),
+		Credentials:      credentials.NewStaticCredentials(accessKey, secretKey, token),
 		Endpoint:         &endpoint,
 		Region:           &region,
 		DisableSSL:       aws.Bool(uri.Scheme == "http"),
diff --git a/pkg/object/redis.go b/pkg/object/redis.go
index 7aed71b4..7eff5e98 100644
--- a/pkg/object/redis.go
+++ b/pkg/object/redis.go
@@ -160,7 +160,7 @@ func (t *redisStore) Head(key string) (Object, error) {
 	}, err
 }
 
-func newRedis(url, user, passwd string) (ObjectStorage, error) {
+func newRedis(url, user, passwd, token string) (ObjectStorage, error) {
 	opt, err := redis.ParseURL(url)
 	if err != nil {
 		return nil, fmt.Errorf("parse %s: %s", url, err)
diff --git a/pkg/object/s3.go b/pkg/object/s3.go
index 95ef8297..e618232a 100644
--- a/pkg/object/s3.go
+++ b/pkg/object/s3.go
@@ -341,7 +341,7 @@ func parseRegion(endpoint string) string {
 	return region
 }
 
-func newS3(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newS3(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		if len(strings.Split(endpoint, ".")) > 1 && !strings.HasSuffix(endpoint, ".amazonaws.com") {
 			endpoint = fmt.Sprintf("http://%s", endpoint)
@@ -436,7 +436,7 @@ func newS3(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 	if accessKey == "anonymous" {
 		awsConfig.Credentials = credentials.AnonymousCredentials
 	} else if accessKey != "" {
-		awsConfig.Credentials = credentials.NewStaticCredentials(accessKey, secretKey, "")
+		awsConfig.Credentials = credentials.NewStaticCredentials(accessKey, secretKey, token)
 	}
 	if ep != "" {
 		awsConfig.Endpoint = aws.String(ep)
diff --git a/pkg/object/scs.go b/pkg/object/scs.go
index bfa01d71..351e4fc4 100644
--- a/pkg/object/scs.go
+++ b/pkg/object/scs.go
@@ -167,7 +167,7 @@ func (s *scsClient) ListUploads(marker string) ([]*PendingPart, string, error) {
 	return nil, "", notSupported
 }
 
-func newSCS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newSCS(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
diff --git a/pkg/object/scw.go b/pkg/object/scw.go
index 33493245..51ff8b74 100644
--- a/pkg/object/scw.go
+++ b/pkg/object/scw.go
@@ -39,7 +39,7 @@ func (s *scw) String() string {
 	return fmt.Sprintf("scw://%s/", s.s3client.bucket)
 }
 
-func newScw(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newScw(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
@@ -66,7 +66,7 @@ func newScw(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 		DisableSSL:       aws.Bool(!ssl),
 		S3ForcePathStyle: aws.Bool(false),
 		HTTPClient:       httpClient,
-		Credentials:      credentials.NewStaticCredentials(accessKey, secretKey, ""),
+		Credentials:      credentials.NewStaticCredentials(accessKey, secretKey, token),
 	}
 
 	ses, err := session.NewSession(awsConfig)
diff --git a/pkg/object/sftp.go b/pkg/object/sftp.go
index 8ae4eed6..c9f4f027 100644
--- a/pkg/object/sftp.go
+++ b/pkg/object/sftp.go
@@ -431,7 +431,7 @@ func SshInteractive(user, instruction string, questions []string, echos []bool)
 	return answers, nil
 }
 
-func newSftp(endpoint, username, pass string) (ObjectStorage, error) {
+func newSftp(endpoint, username, pass, token string) (ObjectStorage, error) {
 	idx := strings.LastIndex(endpoint, ":")
 	host, port, err := net.SplitHostPort(endpoint[:idx])
 	if err != nil && strings.Contains(err.Error(), "missing port") {
diff --git a/pkg/object/sharding.go b/pkg/object/sharding.go
index 0ba70393..8fea94e1 100644
--- a/pkg/object/sharding.go
+++ b/pkg/object/sharding.go
@@ -190,7 +190,7 @@ func (s *sharded) CompleteUpload(key string, uploadID string, parts []*Part) err
 	return s.pick(key).CompleteUpload(key, uploadID, parts)
 }
 
-func NewSharded(name, endpoint, ak, sk string, shards int) (ObjectStorage, error) {
+func NewSharded(name, endpoint, ak, sk, token string, shards int) (ObjectStorage, error) {
 	stores := make([]ObjectStorage, shards)
 	var err error
 	for i := range stores {
@@ -198,7 +198,7 @@ func NewSharded(name, endpoint, ak, sk string, shards int) (ObjectStorage, error
 		if strings.HasSuffix(ep, "%!(EXTRA int=0)") {
 			return nil, fmt.Errorf("can not generate different endpoint using %s", endpoint)
 		}
-		stores[i], err = CreateStorage(name, ep, ak, sk)
+		stores[i], err = CreateStorage(name, ep, ak, sk, token)
 		if err != nil {
 			return nil, err
 		}
diff --git a/pkg/object/space.go b/pkg/object/space.go
index 21f1420c..6db6c4ba 100644
--- a/pkg/object/space.go
+++ b/pkg/object/space.go
@@ -38,7 +38,7 @@ func (s *space) String() string {
 	return fmt.Sprintf("space://%s/", s.s3client.bucket)
 }
 
-func newSpace(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newSpace(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
@@ -55,7 +55,7 @@ func newSpace(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 		DisableSSL:       aws.Bool(!ssl),
 		S3ForcePathStyle: aws.Bool(false),
 		HTTPClient:       httpClient,
-		Credentials:      credentials.NewStaticCredentials(accessKey, secretKey, ""),
+		Credentials:      credentials.NewStaticCredentials(accessKey, secretKey, token),
 	}
 
 	ses, err := session.NewSession(awsConfig)
diff --git a/pkg/object/speedy.go b/pkg/object/speedy.go
index 41ca3fd3..fc0c16fa 100644
--- a/pkg/object/speedy.go
+++ b/pkg/object/speedy.go
@@ -102,7 +102,7 @@ func (s *speedy) List(prefix, marker string, limit int64) ([]Object, error) {
 	return objs, nil
 }
 
-func newSpeedy(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newSpeedy(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
diff --git a/pkg/object/sql.go b/pkg/object/sql.go
index f1228cc7..3d8250b1 100644
--- a/pkg/object/sql.go
+++ b/pkg/object/sql.go
@@ -184,21 +184,21 @@ func newSQLStore(driver, addr, user, password string) (ObjectStorage, error) {
 }
 
 func init() {
-	Register("sqlite3", func(addr, user, pass string) (ObjectStorage, error) {
+	Register("sqlite3", func(addr, user, pass, token string) (ObjectStorage, error) {
 		p := strings.Index(addr, "://")
 		if p > 0 {
 			addr = addr[p+3:]
 		}
 		return newSQLStore("sqlite3", addr, user, pass)
 	})
-	Register("mysql", func(addr, user, pass string) (ObjectStorage, error) {
+	Register("mysql", func(addr, user, pass, token string) (ObjectStorage, error) {
 		p := strings.Index(addr, "://")
 		if p > 0 {
 			addr = addr[p+3:]
 		}
 		return newSQLStore("mysql", addr, user, pass)
 	})
-	Register("postgres", func(addr, user, pass string) (ObjectStorage, error) {
+	Register("postgres", func(addr, user, pass, token string) (ObjectStorage, error) {
 		return newSQLStore("postgres", addr, user, pass)
 	})
 }
diff --git a/pkg/object/swift.go b/pkg/object/swift.go
index 37fdba57..e4cfe20c 100644
--- a/pkg/object/swift.go
+++ b/pkg/object/swift.go
@@ -103,7 +103,7 @@ func (s *swiftOSS) Head(key string) (Object, error) {
 	}, err
 }
 
-func newSwiftOSS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newSwiftOSS(endpoint, username, apiKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("http://%s", endpoint)
 	}
@@ -126,9 +126,10 @@ func newSwiftOSS(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 	authURL := uri.Scheme + "://" + host + "/auth/v1.0"
 
 	conn := swift.Connection{
-		UserName: accessKey,
-		ApiKey:   secretKey,
-		AuthUrl:  authURL,
+		UserName:  username,
+		ApiKey:    apiKey,
+		AuthToken: token,
+		AuthUrl:   authURL,
 	}
 	err = conn.Authenticate()
 	if err != nil {
diff --git a/pkg/object/tikv.go b/pkg/object/tikv.go
index c303ce5e..32c6a181 100644
--- a/pkg/object/tikv.go
+++ b/pkg/object/tikv.go
@@ -110,7 +110,7 @@ func (t *tikv) List(prefix, marker string, limit int64) ([]Object, error) {
 	return objs, nil
 }
 
-func newTiKV(endpoint, accesskey, secretkey string) (ObjectStorage, error) {
+func newTiKV(endpoint, accesskey, secretkey, token string) (ObjectStorage, error) {
 	var plvl string // TiKV (PingCap) uses uber-zap logging, make it less verbose
 	switch logger.Level {
 	case logrus.TraceLevel:
diff --git a/pkg/object/ufile.go b/pkg/object/ufile.go
index 48897fa8..e5cdd707 100644
--- a/pkg/object/ufile.go
+++ b/pkg/object/ufile.go
@@ -321,7 +321,7 @@ func (u *ufile) ListUploads(marker string) ([]*PendingPart, string, error) {
 	return parts, out.NextMarker, nil
 }
 
-func newUFile(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newUFile(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
diff --git a/pkg/object/upyun.go b/pkg/object/upyun.go
index 76419a27..ac912ffc 100644
--- a/pkg/object/upyun.go
+++ b/pkg/object/upyun.go
@@ -129,7 +129,7 @@ func (u *up) List(prefix, marker string, limit int64) ([]Object, error) {
 	return nil, u.err
 }
 
-func newUpyun(endpoint, user, passwd string) (ObjectStorage, error) {
+func newUpyun(endpoint, user, passwd, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
diff --git a/pkg/object/wasabi.go b/pkg/object/wasabi.go
index 352fb513..a9306b76 100644
--- a/pkg/object/wasabi.go
+++ b/pkg/object/wasabi.go
@@ -38,7 +38,7 @@ func (s *wasabi) String() string {
 	return fmt.Sprintf("wasabi://%s/", s.s3client.bucket)
 }
 
-func newWasabi(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
+func newWasabi(endpoint, accessKey, secretKey, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("https://%s", endpoint)
 	}
@@ -58,7 +58,7 @@ func newWasabi(endpoint, accessKey, secretKey string) (ObjectStorage, error) {
 		DisableSSL:       aws.Bool(!ssl),
 		S3ForcePathStyle: aws.Bool(false),
 		HTTPClient:       httpClient,
-		Credentials:      credentials.NewStaticCredentials(accessKey, secretKey, ""),
+		Credentials:      credentials.NewStaticCredentials(accessKey, secretKey, token),
 	}
 
 	ses, err := session.NewSession(awsConfig)
diff --git a/pkg/object/webdav.go b/pkg/object/webdav.go
index 8e02a375..7d3adacf 100644
--- a/pkg/object/webdav.go
+++ b/pkg/object/webdav.go
@@ -186,7 +186,7 @@ func (w *webdav) ListAll(prefix, marker string) (<-chan Object, error) {
 	return listed, nil
 }
 
-func newWebDAV(endpoint, user, passwd string) (ObjectStorage, error) {
+func newWebDAV(endpoint, user, passwd, token string) (ObjectStorage, error) {
 	if !strings.Contains(endpoint, "://") {
 		endpoint = fmt.Sprintf("http://%s", endpoint)
 	}


Test patch:
diff --git a/cmd/config_test.go b/cmd/config_test.go
index 51a04edd..31c363cd 100644
--- a/cmd/config_test.go
+++ b/cmd/config_test.go
@@ -65,7 +65,7 @@ func TestConfig(t *testing.T) {
 	if err = Main([]string{"", "config", testMeta, "--capacity", "10", "--inodes", "1000000"}); err != nil {
 		t.Fatalf("config: %s", err)
 	}
-	if err = Main([]string{"", "config", testMeta, "--bucket", "/tmp/newBucket", "--access-key", "testAK", "--secret-key", "testSK"}); err != nil {
+	if err = Main([]string{"", "config", testMeta, "--bucket", "/tmp/newBucket", "--access-key", "testAK", "--secret-key", "testSK", "--session-token", "token"}); err != nil {
 		t.Fatalf("config: %s", err)
 	}
 	if data, err = getStdout([]string{"", "config", testMeta}); err != nil {
@@ -75,7 +75,7 @@ func TestConfig(t *testing.T) {
 		t.Fatalf("json unmarshal: %s", err)
 	}
 	if format.Capacity != 10<<30 || format.Inodes != 1000000 ||
-		format.Bucket != "/tmp/newBucket/" || format.AccessKey != "testAK" || format.SecretKey != "removed" {
+		format.Bucket != "/tmp/newBucket/" || format.AccessKey != "testAK" || format.SecretKey != "removed" || format.SessionToken != "removed" {
 		t.Fatalf("unexpect format: %+v", format)
 	}
 }
diff --git a/cmd/sync_test.go b/cmd/sync_test.go
index b809c1d5..dbd0496b 100644
--- a/cmd/sync_test.go
+++ b/cmd/sync_test.go
@@ -33,7 +33,7 @@ func TestSync(t *testing.T) {
 	minioDir := "synctest"
 	localDir := "/tmp/synctest"
 	defer os.RemoveAll(localDir)
-	storage, err := object.CreateStorage("minio", os.Getenv("MINIO_TEST_BUCKET"), os.Getenv("MINIO_ACCESS_KEY"), os.Getenv("MINIO_SECRET_KEY"))
+	storage, err := object.CreateStorage("minio", os.Getenv("MINIO_TEST_BUCKET"), os.Getenv("MINIO_ACCESS_KEY"), os.Getenv("MINIO_SECRET_KEY"), "")
 	if err != nil {
 		t.Fatalf("create storage failed: %v", err)
 	}
diff --git a/pkg/chunk/cached_store_test.go b/pkg/chunk/cached_store_test.go
index dd3181b0..70c1e8b8 100644
--- a/pkg/chunk/cached_store_test.go
+++ b/pkg/chunk/cached_store_test.go
@@ -104,7 +104,7 @@ var defaultConf = Config{
 }
 
 func TestStoreDefault(t *testing.T) {
-	mem, _ := object.CreateStorage("mem", "", "", "")
+	mem, _ := object.CreateStorage("mem", "", "", "", "")
 	_ = os.RemoveAll(defaultConf.CacheDir)
 	store := NewCachedStore(mem, defaultConf, nil)
 	testStore(t, store)
@@ -117,7 +117,7 @@ func TestStoreDefault(t *testing.T) {
 }
 
 func TestStoreMemCache(t *testing.T) {
-	mem, _ := object.CreateStorage("mem", "", "", "")
+	mem, _ := object.CreateStorage("mem", "", "", "", "")
 	conf := defaultConf
 	conf.CacheDir = "memory"
 	store := NewCachedStore(mem, conf, nil)
@@ -130,7 +130,7 @@ func TestStoreMemCache(t *testing.T) {
 	}
 }
 func TestStoreCompressed(t *testing.T) {
-	mem, _ := object.CreateStorage("mem", "", "", "")
+	mem, _ := object.CreateStorage("mem", "", "", "", "")
 	conf := defaultConf
 	conf.Compress = "lz4"
 	conf.AutoCreate = false
@@ -139,7 +139,7 @@ func TestStoreCompressed(t *testing.T) {
 }
 
 func TestStoreLimited(t *testing.T) {
-	mem, _ := object.CreateStorage("mem", "", "", "")
+	mem, _ := object.CreateStorage("mem", "", "", "", "")
 	conf := defaultConf
 	conf.UploadLimit = 1 << 20
 	conf.DownloadLimit = 1 << 20
@@ -148,7 +148,7 @@ func TestStoreLimited(t *testing.T) {
 }
 
 func TestStoreFull(t *testing.T) {
-	mem, _ := object.CreateStorage("mem", "", "", "")
+	mem, _ := object.CreateStorage("mem", "", "", "", "")
 	conf := defaultConf
 	conf.FreeSpace = 0.9999
 	store := NewCachedStore(mem, conf, nil)
@@ -156,7 +156,7 @@ func TestStoreFull(t *testing.T) {
 }
 
 func TestStoreSmallBuffer(t *testing.T) {
-	mem, _ := object.CreateStorage("mem", "", "", "")
+	mem, _ := object.CreateStorage("mem", "", "", "", "")
 	conf := defaultConf
 	conf.BufferSize = 1 << 20
 	store := NewCachedStore(mem, conf, nil)
@@ -164,7 +164,7 @@ func TestStoreSmallBuffer(t *testing.T) {
 }
 
 func TestStoreAsync(t *testing.T) {
-	mem, _ := object.CreateStorage("mem", "", "", "")
+	mem, _ := object.CreateStorage("mem", "", "", "", "")
 	conf := defaultConf
 	conf.Writeback = true
 	p := filepath.Join(conf.CacheDir, stagingDir, "chunks/0/0/123_0_4")
@@ -186,7 +186,7 @@ func TestStoreAsync(t *testing.T) {
 }
 
 func TestStoreDelayed(t *testing.T) {
-	mem, _ := object.CreateStorage("mem", "", "", "")
+	mem, _ := object.CreateStorage("mem", "", "", "", "")
 	conf := defaultConf
 	conf.Writeback = true
 	conf.UploadDelay = time.Millisecond * 200
@@ -204,7 +204,7 @@ func TestStoreDelayed(t *testing.T) {
 }
 
 func TestStoreMultiBuckets(t *testing.T) {
-	mem, _ := object.CreateStorage("mem", "", "", "")
+	mem, _ := object.CreateStorage("mem", "", "", "", "")
 	conf := defaultConf
 	conf.HashPrefix = true
 	store := NewCachedStore(mem, conf, nil)
@@ -212,7 +212,7 @@ func TestStoreMultiBuckets(t *testing.T) {
 }
 
 func TestFillCache(t *testing.T) {
-	mem, _ := object.CreateStorage("mem", "", "", "")
+	mem, _ := object.CreateStorage("mem", "", "", "", "")
 	conf := defaultConf
 	conf.CacheSize = 10
 	_ = os.RemoveAll(conf.CacheDir)
@@ -246,7 +246,7 @@ func TestFillCache(t *testing.T) {
 }
 
 func BenchmarkCachedRead(b *testing.B) {
-	blob, _ := object.CreateStorage("mem", "", "", "")
+	blob, _ := object.CreateStorage("mem", "", "", "", "")
 	config := defaultConf
 	config.BlockSize = 4 << 20
 	store := NewCachedStore(blob, config, nil)
@@ -269,7 +269,7 @@ func BenchmarkCachedRead(b *testing.B) {
 }
 
 func BenchmarkUncachedRead(b *testing.B) {
-	blob, _ := object.CreateStorage("mem", "", "", "")
+	blob, _ := object.CreateStorage("mem", "", "", "", "")
 	config := defaultConf
 	config.BlockSize = 4 << 20
 	config.CacheSize = 0
diff --git a/pkg/fs/fs_test.go b/pkg/fs/fs_test.go
index 73f4c1ca..2d3725d8 100644
--- a/pkg/fs/fs_test.go
+++ b/pkg/fs/fs_test.go
@@ -288,7 +288,7 @@ func createTestFS(t *testing.T) *FileSystem {
 		AttrTimeout:     time.Millisecond * 100,
 		AccessLog:       "/tmp/juicefs.access.log",
 	}
-	objStore, _ := object.CreateStorage("mem", "", "", "")
+	objStore, _ := object.CreateStorage("mem", "", "", "", "")
 	store := chunk.NewCachedStore(objStore, *conf.Chunk, nil)
 	jfs, err := NewFileSystem(&conf, m, store)
 	if err != nil {
diff --git a/pkg/fuse/fuse_test.go b/pkg/fuse/fuse_test.go
index 75e3460c..b0b79333 100644
--- a/pkg/fuse/fuse_test.go
+++ b/pkg/fuse/fuse_test.go
@@ -84,7 +84,7 @@ func mount(url, mp string) {
 		CacheDir:   "memory",
 	}
 
-	blob, err := object.CreateStorage(strings.ToLower(format.Storage), format.Bucket, format.AccessKey, format.SecretKey)
+	blob, err := object.CreateStorage(strings.ToLower(format.Storage), format.Bucket, format.AccessKey, format.SecretKey, format.SessionToken)
 	if err != nil {
 		log.Fatalf("object storage: %s", err)
 	}
diff --git a/pkg/meta/config_test.go b/pkg/meta/config_test.go
index 61678163..fe6724d7 100644
--- a/pkg/meta/config_test.go
+++ b/pkg/meta/config_test.go
@@ -16,29 +16,39 @@
 
 package meta
 
-import "testing"
+import (
+	"strings"
+	"testing"
+)
 
 func TestRemoveSecret(t *testing.T) {
-	format := Format{Name: "test", SecretKey: "testSecret", EncryptKey: "testEncrypt"}
+	format := Format{Name: "test", SecretKey: "testSecret", EncryptKey: "testEncrypt", SessionToken: "token"}
+	if err := format.Encrypt(); err != nil {
+		t.Fatal(err)
+	}
 
 	format.RemoveSecret()
-	if format.SecretKey != "removed" || format.EncryptKey != "removed" {
+	if format.SecretKey != "removed" || format.EncryptKey != "removed" || format.SessionToken != "removed" {
 		t.Fatalf("invalid format: %+v", format)
 	}
+
+	if err := format.Decrypt(); err != nil && !strings.Contains(err.Error(), "secret was removed") {
+		t.Fatal(err)
+	}
 }
 
 func TestEncrypt(t *testing.T) {
-	format := Format{Name: "test", SecretKey: "testSecret", EncryptKey: "testEncrypt"}
+	format := Format{Name: "test", SecretKey: "testSecret", SessionToken: "token", EncryptKey: "testEncrypt"}
 	if err := format.Encrypt(); err != nil {
 		t.Fatalf("Format encrypt: %s", err)
 	}
-	if format.SecretKey == "testSecret" || format.EncryptKey == "testEncrypt" {
+	if format.SecretKey == "testSecret" || format.SessionToken == "token" || format.EncryptKey == "testEncrypt" {
 		t.Fatalf("invalid format: %+v", format)
 	}
 	if err := format.Decrypt(); err != nil {
 		t.Fatalf("Format decrypt: %s", err)
 	}
-	if format.SecretKey != "testSecret" || format.EncryptKey != "testEncrypt" {
+	if format.SecretKey != "testSecret" || format.SessionToken != "token" || format.EncryptKey != "testEncrypt" {
 		t.Fatalf("invalid format: %+v", format)
 	}
 }
diff --git a/pkg/object/encrypt_test.go b/pkg/object/encrypt_test.go
index 02795cf3..81c53c66 100644
--- a/pkg/object/encrypt_test.go
+++ b/pkg/object/encrypt_test.go
@@ -137,7 +137,7 @@ func TestAESGCM(t *testing.T) {
 }
 
 func TestEncryptedStore(t *testing.T) {
-	s, _ := CreateStorage("mem", "", "", "")
+	s, _ := CreateStorage("mem", "", "", "", "")
 	kc := NewRSAEncryptor(testkey)
 	dc := NewAESEncryptor(kc)
 	es := NewEncrypted(s, dc)
diff --git a/pkg/object/filesystem_test.go b/pkg/object/filesystem_test.go
index af343b85..35d33287 100644
--- a/pkg/object/filesystem_test.go
+++ b/pkg/object/filesystem_test.go
@@ -44,7 +44,7 @@ func testKeysEqual(objs []Object, expectedKeys []string) error {
 }
 
 func TestDisk2(t *testing.T) {
-	s, _ := newDisk("/tmp/abc/", "", "")
+	s, _ := newDisk("/tmp/abc/", "", "", "")
 	testFileSystem(t, s)
 }
 
@@ -52,7 +52,7 @@ func TestSftp2(t *testing.T) {
 	if os.Getenv("SFTP_HOST") == "" {
 		t.SkipNow()
 	}
-	sftp, _ := newSftp(os.Getenv("SFTP_HOST"), os.Getenv("SFTP_USER"), os.Getenv("SFTP_PASS"))
+	sftp, _ := newSftp(os.Getenv("SFTP_HOST"), os.Getenv("SFTP_USER"), os.Getenv("SFTP_PASS"), "")
 	testFileSystem(t, sftp)
 }
 
@@ -60,7 +60,7 @@ func TestHDFS2(t *testing.T) {
 	if os.Getenv("HDFS_ADDR") == "" {
 		t.Skip()
 	}
-	dfs, _ := newHDFS(os.Getenv("HDFS_ADDR"), "", "")
+	dfs, _ := newHDFS(os.Getenv("HDFS_ADDR"), "", "", "")
 	testFileSystem(t, dfs)
 }
 
diff --git a/pkg/object/object_storage_test.go b/pkg/object/object_storage_test.go
index 33c102a3..54903ce9 100644
--- a/pkg/object/object_storage_test.go
+++ b/pkg/object/object_storage_test.go
@@ -263,12 +263,12 @@ func testStorage(t *testing.T, s ObjectStorage) {
 }
 
 func TestMem(t *testing.T) {
-	m, _ := newMem("", "", "")
+	m, _ := newMem("", "", "", "")
 	testStorage(t, m)
 }
 
 func TestDisk(t *testing.T) {
-	s, _ := newDisk("/tmp/abc/", "", "")
+	s, _ := newDisk("/tmp/abc/", "", "", "")
 	testStorage(t, s)
 }
 
@@ -277,7 +277,7 @@ func TestQingStor(t *testing.T) {
 		t.SkipNow()
 	}
 	s, _ := newQingStor(os.Getenv("QY_ENDPOINT"),
-		os.Getenv("QY_ACCESS_KEY"), os.Getenv("QY_SECRET_KEY"))
+		os.Getenv("QY_ACCESS_KEY"), os.Getenv("QY_SECRET_KEY"), "")
 	testStorage(t, s)
 
 	//private cloud
@@ -285,7 +285,7 @@ func TestQingStor(t *testing.T) {
 		t.SkipNow()
 	}
 	s2, _ := newQingStor("http://test.jn1.is.shanhe.com",
-		os.Getenv("PRIVATE_QY_ACCESS_KEY"), os.Getenv("PRIVATE_QY_SECRET_KEY"))
+		os.Getenv("PRIVATE_QY_ACCESS_KEY"), os.Getenv("PRIVATE_QY_SECRET_KEY"), "")
 	testStorage(t, s2)
 }
 
@@ -294,7 +294,9 @@ func TestS3(t *testing.T) {
 		t.SkipNow()
 	}
 	s, _ := newS3(os.Getenv("AWS_ENDPOINT"),
-		os.Getenv("AWS_ACCESS_KEY_ID"), os.Getenv("AWS_SECRET_ACCESS_KEY"))
+		os.Getenv("AWS_ACCESS_KEY_ID"),
+		os.Getenv("AWS_SECRET_ACCESS_KEY"),
+		os.Getenv("AWS_SESSION_TOKEN"))
 	testStorage(t, s)
 }
 
@@ -303,7 +305,8 @@ func TestOSS(t *testing.T) {
 		t.SkipNow()
 	}
 	s, _ := newOSS(os.Getenv("ALICLOUD_ENDPOINT"),
-		os.Getenv("ALICLOUD_ACCESS_KEY_ID"), os.Getenv("ALICLOUD_ACCESS_KEY_SECRET"))
+		os.Getenv("ALICLOUD_ACCESS_KEY_ID"),
+		os.Getenv("ALICLOUD_ACCESS_KEY_SECRET"), "")
 	testStorage(t, s)
 }
 
@@ -312,7 +315,7 @@ func TestUFile(t *testing.T) {
 		t.SkipNow()
 	}
 	ufile, _ := newUFile(os.Getenv("UCLOUD_ENDPOINT"),
-		os.Getenv("UCLOUD_PUBLIC_KEY"), os.Getenv("UCLOUD_PRIVATE_KEY"))
+		os.Getenv("UCLOUD_PUBLIC_KEY"), os.Getenv("UCLOUD_PRIVATE_KEY"), "")
 	testStorage(t, ufile)
 }
 
@@ -320,7 +323,7 @@ func TestGS(t *testing.T) {
 	if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") == "" {
 		t.SkipNow()
 	}
-	gs, _ := newGS(os.Getenv("GOOGLE_ENDPOINT"), "", "")
+	gs, _ := newGS(os.Getenv("GOOGLE_ENDPOINT"), "", "", "")
 	testStorage(t, gs)
 }
 
@@ -329,7 +332,7 @@ func TestQiniu(t *testing.T) {
 		t.SkipNow()
 	}
 	qiniu, _ := newQiniu(os.Getenv("QINIU_ENDPOINT"),
-		os.Getenv("QINIU_ACCESS_KEY"), os.Getenv("QINIU_SECRET_KEY"))
+		os.Getenv("QINIU_ACCESS_KEY"), os.Getenv("QINIU_SECRET_KEY"), "")
 	testStorage(t, qiniu)
 	//qiniu, _ = newQiniu("https://test.cn-north-1-s3.qiniu.com",
 	//	os.Getenv("QINIU_ACCESS_KEY"), os.Getenv("QINIU_SECRET_KEY"))
@@ -341,7 +344,7 @@ func TestKS3(t *testing.T) {
 		t.SkipNow()
 	}
 	ks3, _ := newKS3(os.Getenv("KS3_ENDPOINT"),
-		os.Getenv("KS3_ACCESS_KEY"), os.Getenv("KS3_SECRET_KEY"))
+		os.Getenv("KS3_ACCESS_KEY"), os.Getenv("KS3_SECRET_KEY"), "")
 	testStorage(t, ks3)
 }
 
@@ -351,7 +354,7 @@ func TestCOS(t *testing.T) {
 	}
 	cos, _ := newCOS(
 		os.Getenv("COS_ENDPOINT"),
-		os.Getenv("COS_SECRETID"), os.Getenv("COS_SECRETKEY"))
+		os.Getenv("COS_SECRETID"), os.Getenv("COS_SECRETKEY"), "")
 	testStorage(t, cos)
 }
 
@@ -359,8 +362,9 @@ func TestAzure(t *testing.T) {
 	if os.Getenv("AZURE_STORAGE_ACCOUNT") == "" {
 		t.SkipNow()
 	}
+	//https://containersName.core.windows.net
 	abs, _ := newWabs(os.Getenv("AZURE_ENDPOINT"),
-		os.Getenv("AZURE_STORAGE_ACCOUNT"), os.Getenv("AZURE_STORAGE_KEY"))
+		os.Getenv("AZURE_STORAGE_ACCOUNT"), os.Getenv("AZURE_STORAGE_KEY"), "")
 	testStorage(t, abs)
 }
 
@@ -369,7 +373,7 @@ func TestNOS(t *testing.T) {
 		t.SkipNow()
 	}
 	nos, _ := newNOS(os.Getenv("NOS_ENDPOINT"),
-		os.Getenv("NOS_ACCESS_KEY"), os.Getenv("NOS_SECRET_KEY"))
+		os.Getenv("NOS_ACCESS_KEY"), os.Getenv("NOS_SECRET_KEY"), "")
 	testStorage(t, nos)
 }
 
@@ -378,7 +382,7 @@ func TestJSS(t *testing.T) {
 		t.SkipNow()
 	}
 	jss, _ := newJSS(os.Getenv("JSS_ENDPOINT"),
-		os.Getenv("JSS_ACCESS_KEY"), os.Getenv("JSS_SECRET_KEY"))
+		os.Getenv("JSS_ACCESS_KEY"), os.Getenv("JSS_SECRET_KEY"), "")
 	testStorage(t, jss)
 }
 
@@ -387,7 +391,7 @@ func TestSpeedy(t *testing.T) {
 		t.SkipNow()
 	}
 	cos, _ := newSpeedy(os.Getenv("SPEEDY_ENDPOINT"),
-		os.Getenv("SPEEDY_ACCESS_KEY"), os.Getenv("SPEEDY_SECRET_KEY"))
+		os.Getenv("SPEEDY_ACCESS_KEY"), os.Getenv("SPEEDY_SECRET_KEY"), "")
 	testStorage(t, cos)
 }
 
@@ -395,7 +399,7 @@ func TestB2(t *testing.T) {
 	if os.Getenv("B2_ACCOUNT_ID") == "" {
 		t.SkipNow()
 	}
-	b, err := newB2(os.Getenv("B2_ENDPOINT"), os.Getenv("B2_ACCOUNT_ID"), os.Getenv("B2_APP_KEY"))
+	b, err := newB2(os.Getenv("B2_ENDPOINT"), os.Getenv("B2_ACCOUNT_ID"), os.Getenv("B2_APP_KEY"), "")
 	if err != nil {
 		t.Fatalf("create B2: %s", err)
 	}
@@ -406,7 +410,7 @@ func TestSpace(t *testing.T) {
 	if os.Getenv("SPACE_ACCESS_KEY") == "" {
 		t.SkipNow()
 	}
-	b, _ := newSpace(os.Getenv("SPACE_ENDPOINT"), os.Getenv("SPACE_ACCESS_KEY"), os.Getenv("SPACE_SECRET_KEY"))
+	b, _ := newSpace(os.Getenv("SPACE_ENDPOINT"), os.Getenv("SPACE_ACCESS_KEY"), os.Getenv("SPACE_SECRET_KEY"), "")
 	testStorage(t, b)
 }
 
@@ -415,7 +419,7 @@ func TestBOS(t *testing.T) {
 		t.SkipNow()
 	}
 	b, _ := newBOS(os.Getenv("BDCLOUD_ENDPOINT"),
-		os.Getenv("BDCLOUD_ACCESS_KEY"), os.Getenv("BDCLOUD_SECRET_KEY"))
+		os.Getenv("BDCLOUD_ACCESS_KEY"), os.Getenv("BDCLOUD_SECRET_KEY"), "")
 	testStorage(t, b)
 }
 
@@ -423,7 +427,7 @@ func TestSftp(t *testing.T) {
 	if os.Getenv("SFTP_HOST") == "" {
 		t.SkipNow()
 	}
-	b, _ := newSftp(os.Getenv("SFTP_HOST"), os.Getenv("SFTP_USER"), os.Getenv("SFTP_PASS"))
+	b, _ := newSftp(os.Getenv("SFTP_HOST"), os.Getenv("SFTP_USER"), os.Getenv("SFTP_PASS"), "")
 	testStorage(t, b)
 }
 
@@ -432,7 +436,7 @@ func TestOBS(t *testing.T) {
 		t.SkipNow()
 	}
 	b, _ := newOBS(os.Getenv("HWCLOUD_ENDPOINT"),
-		os.Getenv("HWCLOUD_ACCESS_KEY"), os.Getenv("HWCLOUD_SECRET_KEY"))
+		os.Getenv("HWCLOUD_ACCESS_KEY"), os.Getenv("HWCLOUD_SECRET_KEY"), "")
 	testStorage(t, b)
 }
 
@@ -440,7 +444,7 @@ func TestHDFS(t *testing.T) {
 	if os.Getenv("HDFS_ADDR") == "" {
 		t.SkipNow()
 	}
-	dfs, _ := newHDFS(os.Getenv("HDFS_ADDR"), "", "")
+	dfs, _ := newHDFS(os.Getenv("HDFS_ADDR"), "", "", "")
 	testStorage(t, dfs)
 }
 
@@ -449,7 +453,7 @@ func TestOOS(t *testing.T) {
 		t.SkipNow()
 	}
 	b, _ := newOOS(os.Getenv("OOS_ENDPOINT"),
-		os.Getenv("OOS_ACCESS_KEY"), os.Getenv("OOS_SECRET_KEY"))
+		os.Getenv("OOS_ACCESS_KEY"), os.Getenv("OOS_SECRET_KEY"), "")
 	testStorage(t, b)
 }
 
@@ -457,7 +461,7 @@ func TestScw(t *testing.T) {
 	if os.Getenv("SCW_ACCESS_KEY") == "" {
 		t.SkipNow()
 	}
-	b, _ := newScw(os.Getenv("SCW_ENDPOINT"), os.Getenv("SCW_ACCESS_KEY"), os.Getenv("SCW_SECRET_KEY"))
+	b, _ := newScw(os.Getenv("SCW_ENDPOINT"), os.Getenv("SCW_ACCESS_KEY"), os.Getenv("SCW_SECRET_KEY"), "")
 	testStorage(t, b)
 }
 
@@ -465,7 +469,7 @@ func TestMinIO(t *testing.T) {
 	if os.Getenv("MINIO_TEST_BUCKET") == "" {
 		t.SkipNow()
 	}
-	b, _ := newMinio(os.Getenv("MINIO_TEST_BUCKET"), os.Getenv("MINIO_ACCESS_KEY"), os.Getenv("MINIO_SECRET_KEY"))
+	b, _ := newMinio(os.Getenv("MINIO_TEST_BUCKET"), os.Getenv("MINIO_ACCESS_KEY"), os.Getenv("MINIO_SECRET_KEY"), "")
 	testStorage(t, b)
 }
 
@@ -478,7 +482,7 @@ func TestTiKV(t *testing.T) {
 	if os.Getenv("TIKV_ADDR") == "" {
 		t.SkipNow()
 	}
-	s, err := newTiKV(os.Getenv("TIKV_ADDR"), "", "")
+	s, err := newTiKV(os.Getenv("TIKV_ADDR"), "", "", "")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -493,7 +497,7 @@ func TestRedis(t *testing.T) {
 	rdb := redis.NewClient(opt)
 	_ = rdb.FlushDB(context.Background())
 
-	s, err := newRedis(os.Getenv("REDIS_ADDR"), "", "")
+	s, err := newRedis(os.Getenv("REDIS_ADDR"), "", "", "")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -504,7 +508,7 @@ func TestSwift(t *testing.T) {
 	if os.Getenv("SWIFT_ADDR") == "" {
 		t.SkipNow()
 	}
-	s, err := newSwiftOSS(os.Getenv("SWIFT_ADDR"), "", "")
+	s, err := newSwiftOSS(os.Getenv("SWIFT_ADDR"), "", "", "")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -515,12 +519,12 @@ func TestWebDAV(t *testing.T) {
 	if os.Getenv("WEBDAV_TEST_BUCKET") == "" {
 		t.SkipNow()
 	}
-	s, _ := newWebDAV(os.Getenv("WEBDAV_TEST_BUCKET"), "", "")
+	s, _ := newWebDAV(os.Getenv("WEBDAV_TEST_BUCKET"), "", "", "")
 	testStorage(t, s)
 }
 
 func TestEncrypted(t *testing.T) {
-	s, _ := CreateStorage("mem", "", "", "")
+	s, _ := CreateStorage("mem", "", "", "", "")
 	privkey, _ := rsa.GenerateKey(rand.Reader, 2048)
 	kc := NewRSAEncryptor(privkey)
 	dc := NewAESEncryptor(kc)
@@ -532,7 +536,7 @@ func TestMarsharl(t *testing.T) {
 	if os.Getenv("HDFS_ADDR") == "" {
 		t.SkipNow()
 	}
-	s, _ := newHDFS(os.Getenv("HDFS_ADDR"), "", "")
+	s, _ := newHDFS(os.Getenv("HDFS_ADDR"), "", "", "")
 	if err := s.Put("hello", bytes.NewReader([]byte("world"))); err != nil {
 		t.Fatalf("PUT failed: %s", err)
 	}
@@ -561,7 +565,7 @@ func TestMarsharl(t *testing.T) {
 }
 
 func TestSharding(t *testing.T) {
-	s, _ := NewSharded("mem", "%d", "", "", 10)
+	s, _ := NewSharded("mem", "%d", "", "", "", 10)
 	testStorage(t, s)
 }
 
@@ -597,7 +601,7 @@ func TestMySQL(t *testing.T) {
 }
 
 func TestNameString(t *testing.T) {
-	s, _ := newMem("test", "", "")
+	s, _ := newMem("test", "", "", "")
 	s = WithPrefix(s, "a/")
 	s = WithPrefix(s, "b/")
 	if s.String() != "mem://test/a/b/" {
@@ -609,7 +613,7 @@ func TestEtcd(t *testing.T) {
 	if os.Getenv("ETCD_ADDR") == "" {
 		t.SkipNow()
 	}
-	s, _ := newEtcd(os.Getenv("ETCD_ADDR"), "", "")
+	s, _ := newEtcd(os.Getenv("ETCD_ADDR"), "", "", "")
 	testStorage(t, s)
 }
 
@@ -625,7 +629,7 @@ func TestEOS(t *testing.T) {
 	if os.Getenv("EOS_ENDPOINT") == "" {
 		t.SkipNow()
 	}
-	s, _ := newEos(os.Getenv("EOS_ENDPOINT"), os.Getenv("EOS_ACCESS_KEY"), os.Getenv("EOS_SECRET_KEY"))
+	s, _ := newEos(os.Getenv("EOS_ENDPOINT"), os.Getenv("EOS_ACCESS_KEY"), os.Getenv("EOS_SECRET_KEY"), "")
 	testStorage(t, s)
 }
 
@@ -633,7 +637,7 @@ func TestWASABI(t *testing.T) {
 	if os.Getenv("WASABI_ENDPOINT") == "" {
 		t.SkipNow()
 	}
-	s, _ := newWasabi(os.Getenv("WASABI_ENDPOINT"), os.Getenv("WASABI_ACCESS_KEY"), os.Getenv("WASABI_SECRET_KEY"))
+	s, _ := newWasabi(os.Getenv("WASABI_ENDPOINT"), os.Getenv("WASABI_ACCESS_KEY"), os.Getenv("WASABI_SECRET_KEY"), "")
 	testStorage(t, s)
 }
 
@@ -641,7 +645,7 @@ func TestSCS(t *testing.T) {
 	if os.Getenv("SCS_ENDPOINT") == "" {
 		t.SkipNow()
 	}
-	s, _ := newSCS(os.Getenv("SCS_ENDPOINT"), os.Getenv("SCS_ACCESS_KEY"), os.Getenv("SCS_SECRET_KEY"))
+	s, _ := newSCS(os.Getenv("SCS_ENDPOINT"), os.Getenv("SCS_ACCESS_KEY"), os.Getenv("SCS_SECRET_KEY"), "")
 	testStorage(t, s)
 }
 
@@ -649,7 +653,7 @@ func TestIBMCOS(t *testing.T) {
 	if os.Getenv("IBMCOS_ENDPOINT") == "" {
 		t.SkipNow()
 	}
-	s, _ := newIBMCOS(os.Getenv("IBMCOS_ENDPOINT"), os.Getenv("IBMCOS_ACCESS_KEY"), os.Getenv("IBMCOS_SECRET_KEY"))
+	s, _ := newIBMCOS(os.Getenv("IBMCOS_ENDPOINT"), os.Getenv("IBMCOS_ACCESS_KEY"), os.Getenv("IBMCOS_SECRET_KEY"), "")
 	testStorage(t, s)
 }
 
diff --git a/pkg/sync/sync_test.go b/pkg/sync/sync_test.go
index 717d3f89..7606783f 100644
--- a/pkg/sync/sync_test.go
+++ b/pkg/sync/sync_test.go
@@ -38,7 +38,7 @@ func collectAll(c <-chan object.Object) []string {
 
 // nolint:errcheck
 func TestIterator(t *testing.T) {
-	m, _ := object.CreateStorage("mem", "", "", "")
+	m, _ := object.CreateStorage("mem", "", "", "", "")
 	m.Put("a", bytes.NewReader([]byte("a")))
 	m.Put("b", bytes.NewReader([]byte("a")))
 	m.Put("aa", bytes.NewReader([]byte("a")))
@@ -54,7 +54,7 @@ func TestIterator(t *testing.T) {
 	}
 
 	// Single object
-	s, _ := object.CreateStorage("mem", "", "", "")
+	s, _ := object.CreateStorage("mem", "", "", "", "")
 	s.Put("a", bytes.NewReader([]byte("a")))
 	ch, _ = ListAll(s, "", "")
 	keys = collectAll(ch)
@@ -67,7 +67,7 @@ func TestIeratorSingleEmptyKey(t *testing.T) {
 	// utils.SetLogLevel(logrus.DebugLevel)
 
 	// Construct mem storage
-	s, _ := object.CreateStorage("mem", "", "", "")
+	s, _ := object.CreateStorage("mem", "", "", "", "")
 	err := s.Put("abc", bytes.NewReader([]byte("abc")))
 	if err != nil {
 		t.Fatalf("Put error: %q", err)
@@ -109,14 +109,14 @@ func TestSync(t *testing.T) {
 		Quiet:     true,
 	}
 	os.Args = []string{"--include", "a[1-9]", "--exclude", "a*", "--exclude", "c*"}
-	a, _ := object.CreateStorage("file", "/tmp/a/", "", "")
+	a, _ := object.CreateStorage("file", "/tmp/a/", "", "", "")
 	a.Put("a1", bytes.NewReader([]byte("a1")))
 	a.Put("a2", bytes.NewReader([]byte("a2")))
 	a.Put("abc", bytes.NewReader([]byte("abc")))
 	a.Put("c1", bytes.NewReader([]byte("c1")))
 	a.Put("c2", bytes.NewReader([]byte("c2")))
 
-	b, _ := object.CreateStorage("file", "/tmp/b/", "", "")
+	b, _ := object.CreateStorage("file", "/tmp/b/", "", "", "")
 	b.Put("a1", bytes.NewReader([]byte("a1")))
 	b.Put("ba", bytes.NewReader([]byte("a1")))
 
@@ -193,8 +193,8 @@ func TestSyncIncludeAndExclude(t *testing.T) {
 		Quiet:     true,
 		Exclude:   []string{"1"},
 	}
-	a, _ := object.CreateStorage("file", "/tmp/a/", "", "")
-	b, _ := object.CreateStorage("file", "/tmp/b/", "", "")
+	a, _ := object.CreateStorage("file", "/tmp/a/", "", "", "")
+	b, _ := object.CreateStorage("file", "/tmp/b/", "", "", "")
 
 	simple := []string{"a1/z1/z2", "a2", "ab1", "ab2", "b1", "b2", "c1", "c2"}
 	testCases := []struct {
@@ -310,14 +310,14 @@ func TestSyncLink(t *testing.T) {
 		_ = os.RemoveAll("/tmp/b")
 	}()
 
-	a, _ := object.CreateStorage("file", "/tmp/a/", "", "")
+	a, _ := object.CreateStorage("file", "/tmp/a/", "", "", "")
 	a.Put("a1", bytes.NewReader([]byte("test")))
 	as := a.(object.SupportSymlink)
 	as.Symlink("/tmp/a/a1", "l1")
 	as.Symlink("./../a1", "d1/l2")
 	as.Symlink("./../notExist", "l3")
 
-	b, _ := object.CreateStorage("file", "/tmp/b/", "", "")
+	b, _ := object.CreateStorage("file", "/tmp/b/", "", "", "")
 	bs := b.(object.SupportSymlink)
 	bs.Symlink("/tmp/b/a1", "l1")
 
@@ -369,13 +369,13 @@ func TestSyncLinkWithOutFollow(t *testing.T) {
 		_ = os.RemoveAll("/tmp/b")
 	}()
 
-	a, _ := object.CreateStorage("file", "/tmp/a/", "", "")
+	a, _ := object.CreateStorage("file", "/tmp/a/", "", "", "")
 	a.Put("a1", bytes.NewReader([]byte("test")))
 	as := a.(object.SupportSymlink)
 	as.Symlink("/tmp/a/a1", "l1")
 	as.Symlink("./../notExist", "l3")
 
-	b, _ := object.CreateStorage("file", "/tmp/b/", "", "")
+	b, _ := object.CreateStorage("file", "/tmp/b/", "", "", "")
 
 	if err := Sync(a, b, &Config{
 		Threads:     50,
@@ -409,8 +409,8 @@ func TestSingleLink(t *testing.T) {
 		_ = os.RemoveAll("/tmp/b")
 	}()
 	_ = os.Symlink("/tmp/aa", "/tmp/a")
-	a, _ := object.CreateStorage("file", "/tmp/a", "", "")
-	b, _ := object.CreateStorage("file", "/tmp/b", "", "")
+	a, _ := object.CreateStorage("file", "/tmp/a", "", "", "")
+	b, _ := object.CreateStorage("file", "/tmp/b", "", "", "")
 	if err := Sync(a, b, &Config{
 		Threads:     50,
 		Update:      true,
@@ -439,9 +439,9 @@ func TestLimits(t *testing.T) {
 		_ = os.RemoveAll("/tmp/b/")
 		_ = os.RemoveAll("/tmp/c/")
 	}()
-	a, _ := object.CreateStorage("file", "/tmp/a/", "", "")
-	b, _ := object.CreateStorage("file", "/tmp/b/", "", "")
-	c, _ := object.CreateStorage("file", "/tmp/c/", "", "")
+	a, _ := object.CreateStorage("file", "/tmp/a/", "", "", "")
+	b, _ := object.CreateStorage("file", "/tmp/b/", "", "", "")
+	c, _ := object.CreateStorage("file", "/tmp/c/", "", "", "")
 	put := func(storage object.ObjectStorage, keys []string) {
 		for _, key := range keys {
 			if key != "" {
diff --git a/pkg/vfs/compact_test.go b/pkg/vfs/compact_test.go
index b60945ee..2d6190f5 100644
--- a/pkg/vfs/compact_test.go
+++ b/pkg/vfs/compact_test.go
@@ -35,7 +35,7 @@ func TestCompact(t *testing.T) {
 		CacheSize:  10,
 		CacheDir:   "memory",
 	}
-	blob, _ := object.CreateStorage("mem", "", "", "")
+	blob, _ := object.CreateStorage("mem", "", "", "", "")
 	store := chunk.NewCachedStore(blob, cconf, nil)
 
 	// prepare the slices
diff --git a/pkg/vfs/vfs_test.go b/pkg/vfs/vfs_test.go
index 477896bc..ad398713 100644
--- a/pkg/vfs/vfs_test.go
+++ b/pkg/vfs/vfs_test.go
@@ -69,7 +69,7 @@ func createTestVFS() (*VFS, object.ObjectStorage) {
 			CacheDir:   "memory",
 		},
 	}
-	blob, _ := object.CreateStorage("mem", "", "", "")
+	blob, _ := object.CreateStorage("mem", "", "", "", "")
 	registry := prometheus.NewRegistry() // replace default so only JuiceFS metrics are exposed
 	registerer := prometheus.WrapRegistererWithPrefix("juicefs_",
 		prometheus.WrapRegistererWith(prometheus.Labels{"mp": mp, "vol_name": format.Name}, registry))