Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix multi cache dir #2639

Merged
merged 8 commits into from
Feb 27, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions docs/zh/samples/juicefs/juicefs_cache_dir.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# JuiceFSRuntime 缓存配置

如何在 Fluid 中使用 JuiceFS,请参考文档[示例 - 如何在 Fluid 中使用 JuiceFS](juicefs_runtime.md)。本文讲述所有在 Fluid 中有关 JuiceFS 的缓存相关配置。

## 设置多个路径缓存

缓存路径在 JuiceFSRuntime 中的 tiredstore 设置,worker 和 fuse pod 共享相同的配置。

注意:JuiceFS 支持多路径缓存,不支持多级缓存。

```yaml
apiVersion: data.fluid.io/v1alpha1
kind: JuiceFSRuntime
metadata:
name: jfsdemo
spec:
replicas: 1
tieredstore:
levels:
- mediumtype: SSD
path: /mnt/cache1:/mnt/cache2
quota: 40Gi
low: "0.1"
```

其中:
- `spec.tiredstore.levels.path` 可设置为多个路径,以 `:` 分隔,缓存会被分配在这里设置的所有路径下;但不支持通配符;
- `spec.tiredstore.levels.quota` 为缓存对象的总大小,与路径多少无关;
- `spec.tiredstore.levels.low` 为缓存路径的最小剩余空间比例,无论缓存是否达到限额,都会保证缓存路径的剩余空间;
- `spec.tiredstore.levels.mediumtype` 为缓存路径的类型,目前支持 `SSD` 和 `MEM`。


## 单独设置 worker 的缓存路径

默认情况下,worker 和 fuse 的缓存路径都在 `spec.tiredstore.levels.path` 中设置,但是也可以单独设置 worker 的缓存路径。

```yaml
apiVersion: data.fluid.io/v1alpha1
kind: JuiceFSRuntime
metadata:
name: jfsdemo
spec:
worker:
options:
"cache-dir": "/mnt/cache1:/mnt/cache2"
tieredstore:
levels:
- mediumtype: MEM
path: /dev/shm
quota: 500Mi
low: "0.1"
```

其中:
- `spec.worker.options` 为 worker 的挂载参数,缓存路径以 `cache-dir` 为 key,以 `:` 分隔的多个路径;


Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Redis、MySQL、TiKV 等多种数据库中。

## 部署 JuiceFSRuntime 环境

具体部署方法参考文档 [如何在 Fluid 中使用 JuiceFS](./juicefs_runtime.md)。
具体部署方法参考文档 [如何在 Fluid 中使用 JuiceFS](juicefs_runtime.md)。

在 JuiceFSRuntime 和 Dataset 创建成功后,等待 worker pod 启动成功,再进行下面的步骤。

Expand Down Expand Up @@ -63,4 +63,4 @@ root@jfsdemo-worker-0:~#

可以看到 bucket 中的文件已经被同步到了 JuiceFS 中。

最后创建业务 Pod,其中 Pod 使用上面创建的 `Dataset` 的方式为指定同名的 PVC。该步骤与文档 [如何在 Fluid 中使用 JuiceFS](./juicefs_runtime.md) 中一致,这里不再赘述。
最后创建业务 Pod,其中 Pod 使用上面创建的 `Dataset` 的方式为指定同名的 PVC。该步骤与文档 [如何在 Fluid 中使用 JuiceFS](juicefs_runtime.md) 中一致,这里不再赘述。
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ JuiceFS 是一款面向云环境设计的开源高性能共享文件系统,提

## 安装

您可以从 [Fluid Releases](https://github.com/fluid-cloudnative/fluid/releases) 下载最新的 Fluid 安装包。参考 [安装文档](../userguide/install.md) 完成安装。并检查 Fluid 各组件正常运行:
您可以从 [Fluid Releases](https://github.com/fluid-cloudnative/fluid/releases) 下载最新的 Fluid 安装包。参考 [安装文档](../../userguide/install.md) 完成安装。并检查 Fluid 各组件正常运行:

```shell
$ kubectl get po -n fluid-system
Expand Down
16 changes: 8 additions & 8 deletions pkg/controllers/v1alpha1/databackup/implement.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,14 +433,14 @@ func (r *DataBackupReconcilerImplement) generateDataBackupValueFile(ctx reconcil
imagePullSecrets := docker.GetImagePullSecretsFromEnv(common.EnvImagePullSecretsKey)

dataBackup := cdatabackup.DataBackup{
Namespace: databackup.Namespace,
Dataset: databackup.Spec.Dataset,
Name: databackup.Name,
NodeName: nodeName,
Image: image,
JavaEnv: javaEnv,
Workdir: workdir,
RuntimeType: runtimeType,
Namespace: databackup.Namespace,
Dataset: databackup.Spec.Dataset,
Name: databackup.Name,
NodeName: nodeName,
Image: image,
JavaEnv: javaEnv,
Workdir: workdir,
RuntimeType: runtimeType,
ImagePullSecrets: imagePullSecrets,
}
pvcName, path, err := utils.ParseBackupRestorePath(databackup.Spec.BackupPath)
Expand Down
47 changes: 45 additions & 2 deletions pkg/ddc/juicefs/operations/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"time"
Expand All @@ -29,6 +30,10 @@ import (
"github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient"
)

const DirRegexFormat = "^[^\\s]*$"

var DirRegex = regexp.MustCompile(DirRegexFormat)

type JuiceFileUtils struct {
podName string
namespace string
Expand Down Expand Up @@ -184,8 +189,39 @@ func (j JuiceFileUtils) Mkdir(juiceSubPath string) (err error) {
return
}

// DeleteDir delete dir in pod
func (j JuiceFileUtils) DeleteDir(dir string) (err error) {
// DeleteCacheDirs delete cache dir in pod
func (j JuiceFileUtils) DeleteCacheDirs(dirs []string) (err error) {
for _, dir := range dirs {
// cache dir check
match := ValidCacheDir(dir)
if !match {
err = fmt.Errorf("dir %s is not valid", dir)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using the following info?

err = fmt.Errorf("invalid cache directory %s, skip cleaning up", dir)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zwwhdls The code now returns an error but I think we should ignore the error if it's not a valid cache dir.

return
}
}
var (
command = []string{"rm", "-rf"}
stdout string
stderr string
)
command = append(command, dirs...)

stdout, stderr, err = j.exec(command, true)
if err != nil {
err = fmt.Errorf("execute command %v with expectedErr: %v stdout %s and stderr %s", command, err, stdout, stderr)
return
}
return
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To prevent data loss from incorrect directory parameters, I suggest using regular expressions to validate before using "rm -rf" command. Is it possible for this case?


// DeleteCacheDir delete cache dir in pod
func (j JuiceFileUtils) DeleteCacheDir(dir string) (err error) {
// cache dir check
match := ValidCacheDir(dir)
if !match {
err = fmt.Errorf("dir %s is not valid", dir)
return
}
var (
command = []string{"rm", "-rf", dir}
stdout string
Expand Down Expand Up @@ -349,3 +385,10 @@ func (j JuiceFileUtils) QueryMetaDataInfoIntoFile(key KeyOfMetaDataFile, filenam
}
return
}

func ValidCacheDir(dir string) (match bool) {
if !strings.HasSuffix(dir, "raw/chunks") {
return false
}
return DirRegex.MatchString(dir)
}
160 changes: 156 additions & 4 deletions pkg/ddc/juicefs/operations/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ func TestJuiceFileUtils_GetMetric(t *testing.T) {
wrappedUnhookExec()
}

func TestJuiceFileUtils_DeleteDir(t *testing.T) {
func TestJuiceFileUtils_DeleteCacheDirs(t *testing.T) {
ExecCommon := func(a JuiceFileUtils, command []string, verbose bool) (stdout string, stderr string, err error) {
return "juicefs rmr success", "", nil
}
Expand All @@ -252,7 +252,7 @@ func TestJuiceFileUtils_DeleteDir(t *testing.T) {
t.Fatal(err.Error())
}
a := JuiceFileUtils{}
err = a.DeleteDir("")
err = a.DeleteCacheDirs([]string{"/tmp/raw/chunks"})
if err == nil {
t.Error("check failure, want err, got nil")
}
Expand All @@ -262,13 +262,62 @@ func TestJuiceFileUtils_DeleteDir(t *testing.T) {
if err != nil {
t.Fatal(err.Error())
}
err = a.DeleteDir("")
err = a.DeleteCacheDirs([]string{"/tmp/raw/chunks"})
if err != nil {
t.Errorf("check failure, want nil, got err: %v", err)
}
wrappedUnhookExec()
}

func TestJuiceFileUtils_DeleteCacheDir(t *testing.T) {
ExecCommon := func(a JuiceFileUtils, command []string, verbose bool) (stdout string, stderr string, err error) {
return "juicefs rmr success", "", nil
}
ExecErr := func(a JuiceFileUtils, command []string, verbose bool) (stdout string, stderr string, err error) {
return "", "", errors.New("fail to run the command")
}
wrappedUnhookExec := func() {
err := gohook.UnHook(JuiceFileUtils.exec)
if err != nil {
t.Fatal(err.Error())
}
}

a := JuiceFileUtils{}
// no error
err := gohook.Hook(JuiceFileUtils.exec, ExecCommon, nil)
if err != nil {
t.Fatal(err.Error())
}
err = a.DeleteCacheDir("/tmp/raw/chunks")
if err != nil {
t.Errorf("check failure, want nil, got err: %v", err)
}
wrappedUnhookExec()

// dir error
err = gohook.Hook(JuiceFileUtils.exec, ExecErr, nil)
if err != nil {
t.Fatal(err.Error())
}
err = a.DeleteCacheDir("/t mp")
if err == nil || !strings.Contains(err.Error(), "is not valid") {
t.Error("check failure, want err, got nil")
}
wrappedUnhookExec()

// error
err = gohook.Hook(JuiceFileUtils.exec, ExecErr, nil)
if err != nil {
t.Fatal(err.Error())
}
err = a.DeleteCacheDir("/tmp/raw/chunks")
if err == nil {
t.Error("check failure, want err, got nil")
}
wrappedUnhookExec()
}

func TestJuiceFileUtils_GetStatus(t *testing.T) {
ExecCommon := func(a JuiceFileUtils, command []string, verbose bool) (stdout string, stderr string, err error) {
return CommonStatus, "", nil
Expand All @@ -288,7 +337,7 @@ func TestJuiceFileUtils_GetStatus(t *testing.T) {
t.Fatal(err.Error())
}
a := JuiceFileUtils{}
err = a.DeleteDir("")
err = a.DeleteCacheDir("/tmp/raw/chunks")
if err == nil {
t.Error("check failure, want err, got nil")
}
Expand Down Expand Up @@ -504,3 +553,106 @@ func TestAlluxioFileUtils_QueryMetaDataInfoIntoFile(t *testing.T) {
}
wrappedUnhookExec()
}

func TestValidDir(t *testing.T) {
type args struct {
dir string
}
tests := []struct {
name string
args args
wantMatch bool
}{
{
name: "test-normal",
args: args{
dir: "/tmp/raw/chunks",
},
wantMatch: true,
},
{
name: "test1",
args: args{
dir: "/t mp/raw/chunks",
},
wantMatch: false,
},
{
name: "test2",
args: args{
dir: "/t..mp/raw/chunks",
},
wantMatch: true,
},
{
name: "test3",
args: args{
dir: "/t__mp/raw/chunks",
},
wantMatch: true,
},
{
name: "test4",
args: args{
dir: "/t--mp/raw/chunks",
},
wantMatch: true,
},
{
name: "test5",
args: args{
dir: "/",
},
wantMatch: false,
},
{
name: "test6",
args: args{
dir: ".",
},
wantMatch: false,
},
{
name: "test7",
args: args{
dir: "/tttt/raw/chunks",
},
wantMatch: true,
},
{
name: "test8",
args: args{
dir: "//",
},
wantMatch: false,
},
{
name: "test9",
args: args{
dir: "/0/raw/chunks",
},
wantMatch: true,
},
{
name: "test10",
args: args{
dir: "/0/1/raw/chunks",
},
wantMatch: true,
},
{
name: "test11",
args: args{
dir: "/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/0/raw/chunks",
},
wantMatch: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if gotMatch := ValidCacheDir(tt.args.dir); gotMatch != tt.wantMatch {
t.Errorf("ValidDir() = %v, want %v", gotMatch, tt.wantMatch)
}
})
}
}
Loading