From 2c4d3124f7541079ebfd1d3876db70474faa133e Mon Sep 17 00:00:00 2001 From: xliuqq Date: Wed, 22 Apr 2026 23:30:47 +0800 Subject: [PATCH 1/9] support secret mount options Signed-off-by: xliuqq --- api/v1alpha1/cacheruntimeclass_types.go | 8 - api/v1alpha1/zz_generated.deepcopy.go | 52 ++- .../data.fluid.io_cacheruntimeclasses.yaml | 6 - .../data.fluid.io_cacheruntimeclasses.yaml | 6 - .../dev/generic_cache_runtime_integration.md | 9 +- .../dev/generic_cache_runtime_integration.md | 10 +- pkg/common/cacheruntime.go | 14 +- .../cache/engine/cache_engine_suite_test.go | 13 + pkg/ddc/cache/engine/cm.go | 5 +- pkg/ddc/cache/engine/dataset.go | 51 +-- pkg/ddc/cache/engine/setup.go | 18 +- pkg/ddc/cache/engine/status.go | 5 +- pkg/ddc/cache/engine/transform.go | 4 +- pkg/ddc/cache/engine/transform_master.go | 5 +- pkg/ddc/cache/engine/transform_volumes.go | 61 +++ .../cache/engine/transform_volumes_test.go | 415 ++++++++++++++++++ pkg/ddc/cache/engine/transform_worker.go | 5 +- pkg/ddc/cache/engine/ufs.go | 6 +- pkg/ddc/cache/engine/util.go | 15 + test/gha-e2e/curvine/dataset.yaml | 29 +- test/gha-e2e/curvine/mount.yaml | 23 +- 21 files changed, 644 insertions(+), 116 deletions(-) create mode 100644 pkg/ddc/cache/engine/cache_engine_suite_test.go create mode 100644 pkg/ddc/cache/engine/transform_volumes.go create mode 100644 pkg/ddc/cache/engine/transform_volumes_test.go diff --git a/api/v1alpha1/cacheruntimeclass_types.go b/api/v1alpha1/cacheruntimeclass_types.go index 8e8ab3ffb76..c1b56d6ca37 100644 --- a/api/v1alpha1/cacheruntimeclass_types.go +++ b/api/v1alpha1/cacheruntimeclass_types.go @@ -81,10 +81,6 @@ type ExecutionCommonEntry struct { TimeoutSeconds int32 `json:"timeout,omitempty"` } -// EncryptOptionComponentDependency defines the configuration for encrypt option dependency -type EncryptOptionComponentDependency struct { -} - // ExtraResourcesComponentDependency defines the extra resources configuration for component dependencies type ExtraResourcesComponentDependency struct { // ConfigMaps is a list of ConfigMaps in the same namespace to mount into the component @@ -94,10 +90,6 @@ type ExtraResourcesComponentDependency struct { // RuntimeComponentDependencies defines the dependencies required by a CacheRuntime component type RuntimeComponentDependencies struct { - // EncryptOption is the configuration for encrypt option secret mount - // +optional - EncryptOption *EncryptOptionComponentDependency `json:"encryptOption,omitempty"` - // ExtraResources specifies the usage of extra resources such as ConfigMaps // +optional ExtraResources *ExtraResourcesComponentDependency `json:"extraResources,omitempty"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index bd206fcc086..ef5e9798c1e 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -346,6 +346,13 @@ func (in *CacheRuntimeClass) DeepCopyInto(out *CacheRuntimeClass) { (*in).DeepCopyInto(*out) } in.ExtraResources.DeepCopyInto(&out.ExtraResources) + if in.DataOperationSpecs != nil { + in, out := &in.DataOperationSpecs, &out.DataOperationSpecs + *out = make([]DataOperationSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CacheRuntimeClass. @@ -1031,6 +1038,31 @@ func (in *DataMigrateSpec) DeepCopy() *DataMigrateSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DataOperationSpec) DeepCopyInto(out *DataOperationSpec) { + *out = *in + if in.Command != nil { + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Args != nil { + in, out := &in.Args, &out.Args + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataOperationSpec. +func (in *DataOperationSpec) DeepCopy() *DataOperationSpec { + if in == nil { + return nil + } + out := new(DataOperationSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DataProcess) DeepCopyInto(out *DataProcess) { *out = *in @@ -1541,21 +1573,6 @@ func (in *EncryptOption) DeepCopy() *EncryptOption { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EncryptOptionComponentDependency) DeepCopyInto(out *EncryptOptionComponentDependency) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EncryptOptionComponentDependency. -func (in *EncryptOptionComponentDependency) DeepCopy() *EncryptOptionComponentDependency { - if in == nil { - return nil - } - out := new(EncryptOptionComponentDependency) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EncryptOptionSource) DeepCopyInto(out *EncryptOptionSource) { *out = *in @@ -2888,11 +2905,6 @@ func (in *RuntimeComponentDefinition) DeepCopy() *RuntimeComponentDefinition { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RuntimeComponentDependencies) DeepCopyInto(out *RuntimeComponentDependencies) { *out = *in - if in.EncryptOption != nil { - in, out := &in.EncryptOption, &out.EncryptOption - *out = new(EncryptOptionComponentDependency) - **out = **in - } if in.ExtraResources != nil { in, out := &in.ExtraResources, &out.ExtraResources *out = new(ExtraResourcesComponentDependency) diff --git a/charts/fluid/fluid/crds/data.fluid.io_cacheruntimeclasses.yaml b/charts/fluid/fluid/crds/data.fluid.io_cacheruntimeclasses.yaml index 177eb366e32..be2cc842ac1 100644 --- a/charts/fluid/fluid/crds/data.fluid.io_cacheruntimeclasses.yaml +++ b/charts/fluid/fluid/crds/data.fluid.io_cacheruntimeclasses.yaml @@ -72,8 +72,6 @@ spec: properties: dependencies: properties: - encryptOption: - type: object extraResources: properties: configMaps: @@ -3489,8 +3487,6 @@ spec: properties: dependencies: properties: - encryptOption: - type: object extraResources: properties: configMaps: @@ -6906,8 +6902,6 @@ spec: properties: dependencies: properties: - encryptOption: - type: object extraResources: properties: configMaps: diff --git a/config/crd/bases/data.fluid.io_cacheruntimeclasses.yaml b/config/crd/bases/data.fluid.io_cacheruntimeclasses.yaml index 177eb366e32..be2cc842ac1 100644 --- a/config/crd/bases/data.fluid.io_cacheruntimeclasses.yaml +++ b/config/crd/bases/data.fluid.io_cacheruntimeclasses.yaml @@ -72,8 +72,6 @@ spec: properties: dependencies: properties: - encryptOption: - type: object extraResources: properties: configMaps: @@ -3489,8 +3487,6 @@ spec: properties: dependencies: properties: - encryptOption: - type: object extraResources: properties: configMaps: @@ -6906,8 +6902,6 @@ spec: properties: dependencies: properties: - encryptOption: - type: object extraResources: properties: configMaps: diff --git a/docs/en/dev/generic_cache_runtime_integration.md b/docs/en/dev/generic_cache_runtime_integration.md index 81cf7b0dcd2..16765b0b6e1 100644 --- a/docs/en/dev/generic_cache_runtime_integration.md +++ b/docs/en/dev/generic_cache_runtime_integration.md @@ -81,8 +81,7 @@ The component in Topology mainly contains the following content: | Options | Default options, will be overridden by user settings | | | Template | PodTemplateSpec native field | | | Service | Currently only supports Headless | | -| Dependencies | EncryptOption | Whether this component needs Fluid to mount the access keys defined in Dataset for accessing data sources [Not supported in current version], using the keys defined in Dataset for access. | -| | ExtraResources | Whether this component needs to mount additional ConfigMaps (the dependent ConfigMap information is defined in the ExtraResources field of CacheRuntimeClass). | +| Dependencies | ExtraResources | Whether this component needs to mount additional ConfigMaps (the dependent ConfigMap information is defined in the ExtraResources field of CacheRuntimeClass). | | ExecutionEntries| MountUFS | For Master-Worker architecture, when Master is Ready, the underlying file system mount operation needs to be executed. | | ExecutionEntries| ReportSummary | How the cache system defines operations to obtain cache information metrics [Not supported in current version]. | @@ -260,7 +259,7 @@ spec: In cacheruntime, all control plane processes are handled by Fluid. However, as a data caching engine, when providing services, the entire cache system requires **topology**, **data source**, **authentication**, and **cache information**. Fluid will provide this information to components through configuration files based on different Component roles. The component's internal process is responsible for parsing this configuration to perform environment variable configuration, data engine configuration file generation, and other operations. After preparation is complete, the data engine process can be started. For specific parsing details, please refer to the table below: * Taking the above resources as an example, the Config examples mounted by Master/Worker/Client and maintained by Fluid are as follows: - +the `mounts`, `accessModes`, and `targetPath` fields in the JSON are all derived from the Dataset's Spec definition. ```json { @@ -274,6 +273,10 @@ In cacheruntime, all control plane processes are handled by Fluid. However, as a "region_name": "us-east-1", "secret": "minioadmin" }, + "encryptOptions": { + "access-key": "/etc/fluid/secrets/minio-secret/access-key", + "secret-key": "/etc/fluid/secrets/minio-secret/secret-key" + }, "name": "minio", "path": "/minio" } diff --git a/docs/zh/dev/generic_cache_runtime_integration.md b/docs/zh/dev/generic_cache_runtime_integration.md index efa756b0874..5db17c4d6d9 100644 --- a/docs/zh/dev/generic_cache_runtime_integration.md +++ b/docs/zh/dev/generic_cache_runtime_integration.md @@ -81,8 +81,7 @@ Topology中comopent主要包含以下内容 | Options | 默认options,会被用户设置覆盖 | | | Template | PodTemplateSpec 原生字段 | | | Service | 目前仅支持Headless | | -| Dependencies | EncryptOption | 该组件是否需要Fluid为其挂载Dataset中定义的用于访问数据源的访问密钥 【当前版本暂未支持】,使用 Dataset 的定义的密钥进行访问。 | -| | ExtraResources | 该组件是否需要挂载额外的 ConfigMap (其依赖的ConfigMap 信息定义于 CacheRuntimeClass 的 ExtraResources 字段)。 | +| Dependencies | ExtraResources | 该组件是否需要挂载额外的 ConfigMap (其依赖的ConfigMap 信息定义于 CacheRuntimeClass 的 ExtraResources 字段)。 | | ExecutionEntries| MountUFS | 对于Master-Worker架构,当Master Ready时,需要执行底层文件系统的挂载操作。 | | ExecutionEntries| ReportSummary | 缓存系统定义如何获取缓存信息指标的操作 【当前版本暂未支持】。 | @@ -259,8 +258,7 @@ spec: 在cacheruntime中,控制面的所有流程全都有Fluid来负责,但作为数据缓存引擎,提供服务时,需要整个缓存系统中的**拓扑**、**数据源、认证、缓存信息,**Fluid会根据不同的Component角色来通过配置文件的方式提供至组件内部,由组件内部进程负责解析该配置,来进行环境变量配置、数据引擎配置文件生成等操作,准备就绪后,可拉起数据引擎进程,解析过程中具体可参考下表: -* 以上述资源为例,Master/Worker/Client挂载的由Fluid维护的Config示例如下: - +* 以上述资源为例,Master/Worker/Client挂载的由Fluid维护的Config示例如下: 其中,json中的"mounts", "accessModes", "targetPath" 字段信息均是来自于 DataSet 的 Spec 定义。 ```json { @@ -274,6 +272,10 @@ spec: "region_name": "us-east-1", "secret": "minioadmin" }, + "encryptOptions": { + "access-key": "/etc/fluid/secrets/minio-secret/access-key", + "secret-key": "/etc/fluid/secrets/minio-secret/secret-key" + }, "name": "minio", "path": "/minio" } diff --git a/pkg/common/cacheruntime.go b/pkg/common/cacheruntime.go index 8d992a8b168..cfb7cb74bfd 100644 --- a/pkg/common/cacheruntime.go +++ b/pkg/common/cacheruntime.go @@ -77,12 +77,14 @@ type CacheRuntimeConfig struct { // MountConfig defines the mount config about dataset Mounts type MountConfig struct { MountPoint string `json:"mountPoint"` - // TODO: separate encrypt options with mount files for security - Options map[string]string `json:"options,omitempty"` - Name string `json:"name,omitempty"` - Path string `json:"path,omitempty"` - ReadOnly bool `json:"readOnly,omitempty"` - Shared bool `json:"shared,omitempty"` + // Non-encrypted mount options, key is the option name, value is the option value. + Options map[string]string `json:"options,omitempty"` + // Encrypted mount options, key is the option name, value is the secret mount path in container. + EncryptOptions map[string]string `json:"encryptOptions,omitempty"` + Name string `json:"name,omitempty"` + Path string `json:"path,omitempty"` + ReadOnly bool `json:"readOnly,omitempty"` + Shared bool `json:"shared,omitempty"` } type CacheRuntimeComponentConfig struct { diff --git a/pkg/ddc/cache/engine/cache_engine_suite_test.go b/pkg/ddc/cache/engine/cache_engine_suite_test.go new file mode 100644 index 00000000000..2d96d314b6d --- /dev/null +++ b/pkg/ddc/cache/engine/cache_engine_suite_test.go @@ -0,0 +1,13 @@ +package engine + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestCacheEngine(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Cache Engine Suite", Label("cache_engine")) +} diff --git a/pkg/ddc/cache/engine/cm.go b/pkg/ddc/cache/engine/cm.go index 980996defb7..1f03abb6a69 100644 --- a/pkg/ddc/cache/engine/cm.go +++ b/pkg/ddc/cache/engine/cm.go @@ -18,6 +18,7 @@ package engine import ( "encoding/json" + datav1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1" "github.com/fluid-cloudnative/fluid/pkg/common" "github.com/fluid-cloudnative/fluid/pkg/utils" @@ -117,12 +118,12 @@ func (e *CacheEngine) generateRuntimeConfigData(runtime *datav1alpha1.CacheRunti Shared: m.Shared, Path: m.Path, } - // TODO: 默认的加密项的处理?(挂载的形式到 Master 等 Pod 中?)安全性该如何考虑 - options, err := e.generateDatasetMountOptions(&m, dataset.Spec.SharedEncryptOptions, dataset.Spec.SharedOptions) + options, encryptOptions, err := e.generateDatasetMountOptions(&m, dataset.Spec.SharedEncryptOptions, dataset.Spec.SharedOptions) if err != nil { return nil, err } mountCg.Options = options + mountCg.EncryptOptions = encryptOptions mounts = append(mounts, mountCg) } diff --git a/pkg/ddc/cache/engine/dataset.go b/pkg/ddc/cache/engine/dataset.go index df2fc43483f..e3756ec1f6e 100644 --- a/pkg/ddc/cache/engine/dataset.go +++ b/pkg/ddc/cache/engine/dataset.go @@ -18,16 +18,14 @@ package engine import ( "context" - "fmt" + "reflect" + datav1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1" "github.com/fluid-cloudnative/fluid/pkg/common" "github.com/fluid-cloudnative/fluid/pkg/utils" - "github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient" - securityutil "github.com/fluid-cloudnative/fluid/pkg/utils/security" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/retry" - "reflect" ) func (e *CacheEngine) BindToDataset() (err error) { @@ -102,54 +100,45 @@ func (e *CacheEngine) UpdateDatasetStatus(phase datav1alpha1.DatasetPhase) (err } func (e *CacheEngine) generateDatasetMountOptions(m *datav1alpha1.Mount, sharedEncryptOptions []datav1alpha1.EncryptOption, - sharedOptions map[string]string) (map[string]string, error) { + sharedOptions map[string]string) (map[string]string, map[string]string, error) { - // initialize mount options + // initialize mount options, mount options will overwrite shared options. mOptions := map[string]string{} for k, v := range sharedOptions { mOptions[k] = v } - for key, value := range m.Options { mOptions[key] = value } - // if encryptOptions have the same key with options, it will overwrite the corresponding value + // collect encrypt options, mount options will overwrite shared options. + encryptOptions := map[string]string{} var err error - mOptions, err = e.genEncryptOptions(sharedEncryptOptions, mOptions, m.Name) + err = e.collectEncryptOptions(sharedEncryptOptions, encryptOptions, m.Name) if err != nil { - return mOptions, err + return mOptions, encryptOptions, err } - - // gen public encryptOptions - mOptions, err = e.genEncryptOptions(m.EncryptOptions, mOptions, m.Name) + err = e.collectEncryptOptions(m.EncryptOptions, encryptOptions, m.Name) if err != nil { - return mOptions, err + return mOptions, encryptOptions, err } - return mOptions, nil + return mOptions, encryptOptions, nil } -func (e *CacheEngine) genEncryptOptions(EncryptOptions []datav1alpha1.EncryptOption, mOptions map[string]string, name string) (map[string]string, error) { - for _, item := range EncryptOptions { - if _, ok := mOptions[item.Name]; ok { - err := fmt.Errorf("the option %s is set more than one times, please double check the dataset's option and encryptOptions", item.Name) - return mOptions, err - } +func (e *CacheEngine) collectEncryptOptions(encryptOpts []datav1alpha1.EncryptOption, + existingEncryptOpts map[string]string, mountName string) error { - securityutil.UpdateSensitiveKey(item.Name) + for _, item := range encryptOpts { sRef := item.ValueFrom.SecretKeyRef - secret, err := kubeclient.GetSecret(e.Client, sRef.Name, e.namespace) - if err != nil { - e.Log.Error(err, "get secret by mount encrypt options failed", "name", item.Name) - return mOptions, err - } - e.Log.Info("get value from secret", "mount name", name, "secret key", sRef.Key) + // Construct the secret mount path in the container + // The secret will be mounted at /etc/fluid/secrets// + secretPath := getSecretFilePath(sRef.Name, sRef.Key) - v := secret.Data[sRef.Key] - mOptions[item.Name] = string(v) + // Store in map: key is option name, value is secret path + existingEncryptOpts[item.Name] = secretPath } - return mOptions, nil + return nil } diff --git a/pkg/ddc/cache/engine/setup.go b/pkg/ddc/cache/engine/setup.go index eb45ee32420..2a9053ffb48 100644 --- a/pkg/ddc/cache/engine/setup.go +++ b/pkg/ddc/cache/engine/setup.go @@ -76,15 +76,6 @@ func (e *CacheEngine) Setup(ctx cruntime.ReconcileRequestContext) (ready bool, e } } - // dataset mount - if runtimeValue.Master.Enabled { - // currently only support mount ufs for master - err = e.PrepareUFS(runtimeClass.Topology.Master.ExecutionEntries, runtimeValue) - if err != nil { - return false, err - } - } - ready, err = e.CheckAndUpdateRuntimeStatus(runtimeValue) if err != nil { _ = utils.LoggingErrorExceptConflict(e.Log, err, "Failed to check if the runtime is ready", types.NamespacedName{Namespace: e.namespace, Name: e.name}) @@ -94,6 +85,15 @@ func (e *CacheEngine) Setup(ctx cruntime.ReconcileRequestContext) (ready bool, e return } + // dataset mount after runtime ready to ensure master pod is ready for executing commands. + if runtimeValue.Master.Enabled && runtimeClass.Topology.Master.ExecutionEntries != nil { + // currently only support mount ufs for master in master-worker architecture + err = e.PrepareUFS(runtimeClass.Topology.Master.ExecutionEntries.MountUFS, runtimeValue) + if err != nil { + return false, err + } + } + if err = e.BindToDataset(); err != nil { _ = utils.LoggingErrorExceptConflict(e.Log, err, "Failed to bind the dataset", types.NamespacedName{Namespace: e.namespace, Name: e.name}) return false, err diff --git a/pkg/ddc/cache/engine/status.go b/pkg/ddc/cache/engine/status.go index d745a20cb01..5381dfd66bf 100644 --- a/pkg/ddc/cache/engine/status.go +++ b/pkg/ddc/cache/engine/status.go @@ -19,14 +19,15 @@ package engine import ( "context" "fmt" + "reflect" + "time" + fluidapi "github.com/fluid-cloudnative/fluid/api/v1alpha1" "github.com/fluid-cloudnative/fluid/pkg/common" "github.com/fluid-cloudnative/fluid/pkg/ddc/cache/component" "github.com/fluid-cloudnative/fluid/pkg/utils" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/retry" - "reflect" - "time" ) func (e *CacheEngine) setMasterComponentStatus(componentValue *common.CacheRuntimeComponentValue, status *fluidapi.CacheRuntimeStatus) (ready bool, err error) { diff --git a/pkg/ddc/cache/engine/transform.go b/pkg/ddc/cache/engine/transform.go index 709955dce09..28014fd9bf0 100644 --- a/pkg/ddc/cache/engine/transform.go +++ b/pkg/ddc/cache/engine/transform.go @@ -71,11 +71,11 @@ func (e *CacheEngine) transform(dataset *datav1alpha1.Dataset, runtime *datav1al } // transform the master/worker/client - err = e.transformMaster(runtime, runtimeClass, runtimeCommonConfig, runtimeValue) + err = e.transformMaster(dataset, runtime, runtimeClass, runtimeCommonConfig, runtimeValue) if err != nil { return nil, err } - err = e.transformWorker(runtime, runtimeClass, runtimeCommonConfig, runtimeValue) + err = e.transformWorker(dataset, runtime, runtimeClass, runtimeCommonConfig, runtimeValue) if err != nil { return nil, err } diff --git a/pkg/ddc/cache/engine/transform_master.go b/pkg/ddc/cache/engine/transform_master.go index 04112d4f7de..a91e199e8b3 100644 --- a/pkg/ddc/cache/engine/transform_master.go +++ b/pkg/ddc/cache/engine/transform_master.go @@ -21,7 +21,7 @@ import ( "github.com/fluid-cloudnative/fluid/pkg/common" ) -func (e *CacheEngine) transformMaster(runtime *datav1alpha1.CacheRuntime, runtimeClass *datav1alpha1.CacheRuntimeClass, +func (e *CacheEngine) transformMaster(dataset *datav1alpha1.Dataset, runtime *datav1alpha1.CacheRuntime, runtimeClass *datav1alpha1.CacheRuntimeClass, config *CacheRuntimeComponentCommonConfig, value *common.CacheRuntimeValue) error { // TODO: these two field both indicate Master enabled or not, should be combined into one field. if runtimeClass.Topology.Master == nil || runtime.Spec.Master.Disabled { @@ -51,6 +51,9 @@ func (e *CacheEngine) transformMaster(runtime *datav1alpha1.CacheRuntime, runtim return err } + // transform encrypt options to master volumes + e.transformEncryptOptionsToComponentVolumes(dataset, value.Master) + // TODO: transform runtime.Spec.Master, runtimeClass.Topology.Master, dataset.Spec into PodTemplateSpec return nil diff --git a/pkg/ddc/cache/engine/transform_volumes.go b/pkg/ddc/cache/engine/transform_volumes.go new file mode 100644 index 00000000000..20420b58b73 --- /dev/null +++ b/pkg/ddc/cache/engine/transform_volumes.go @@ -0,0 +1,61 @@ +/* +Copyright 2026 The Fluid Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package engine + +import ( + datav1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1" + "github.com/fluid-cloudnative/fluid/pkg/common" + "github.com/fluid-cloudnative/fluid/pkg/utils" + corev1 "k8s.io/api/core/v1" +) + +// transformEncryptOptionsToComponentVolumes transforms encrypt options from dataset spec to component pod volumes +// This function can be reused for both Master and Worker components +func (e *CacheEngine) transformEncryptOptionsToComponentVolumes(dataset *datav1alpha1.Dataset, component *common.CacheRuntimeComponentValue) { + if component == nil || !component.Enabled { + return + } + + for _, m := range dataset.Spec.Mounts { + if common.IsFluidNativeScheme(m.MountPoint) { + continue + } + for _, encryptOpt := range append(dataset.Spec.SharedEncryptOptions, m.EncryptOptions...) { + secretName := encryptOpt.ValueFrom.SecretKeyRef.Name + + volName := getSecretVolumeName(secretName) + volumeToAdd := corev1.Volume{ + Name: volName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: secretName, + }, + }, + } + component.PodTemplateSpec.Spec.Volumes = utils.AppendOrOverrideVolume( + component.PodTemplateSpec.Spec.Volumes, volumeToAdd) + + volumeMountToAdd := corev1.VolumeMount{ + Name: volName, + ReadOnly: true, + MountPath: getSecretMountPath(secretName), + } + component.PodTemplateSpec.Spec.Containers[0].VolumeMounts = utils.AppendOrOverrideVolumeMounts( + component.PodTemplateSpec.Spec.Containers[0].VolumeMounts, volumeMountToAdd) + } + } +} diff --git a/pkg/ddc/cache/engine/transform_volumes_test.go b/pkg/ddc/cache/engine/transform_volumes_test.go new file mode 100644 index 00000000000..612df09e48b --- /dev/null +++ b/pkg/ddc/cache/engine/transform_volumes_test.go @@ -0,0 +1,415 @@ +/* +Copyright 2026 The Fluid Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package engine + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + datav1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1" + "github.com/fluid-cloudnative/fluid/pkg/common" + corev1 "k8s.io/api/core/v1" +) + +// Constants for test values +const ( + testSecretName1 = "test-secret-1" + testSecretName2 = "test-secret-2" + testSecretKey = "access-key" + testMountName = "test-mount" + testMountPoint = "s3://test-bucket" + nativeMountPoint = "local:///mnt/test" +) + +var _ = Describe("CacheEngine Transform Volumes Tests", Label("pkg.ddc.cache.engine.transform_volumes_test.go"), func() { + var ( + engine *CacheEngine + value *common.CacheRuntimeValue + ) + + BeforeEach(func() { + engine = &CacheEngine{} + value = &common.CacheRuntimeValue{ + Master: &common.CacheRuntimeComponentValue{ + Enabled: true, + PodTemplateSpec: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "master", + }, + }, + }, + }, + }, + } + }) + + Describe("transformEncryptOptionsToMasterVolumes", func() { + Context("when dataset has shared encrypt options", func() { + It("should correctly transform shared encrypt options to master volumes", func() { + dataset := &datav1alpha1.Dataset{ + Spec: datav1alpha1.DatasetSpec{ + Mounts: []datav1alpha1.Mount{ + { + MountPoint: testMountPoint, + Name: testMountName, + }, + }, + SharedEncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "aws-access-key-id", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: testSecretName1, + Key: testSecretKey, + }, + }, + }, + }, + }, + } + + engine.transformEncryptOptionsToComponentVolumes(dataset, value.Master) + + Expect(value.Master.PodTemplateSpec.Spec.Volumes).To(HaveLen(1)) + Expect(value.Master.PodTemplateSpec.Spec.Volumes[0].Name).To(Equal("cache-mount-secret-" + testSecretName1)) + Expect(value.Master.PodTemplateSpec.Spec.Volumes[0].Secret.SecretName).To(Equal(testSecretName1)) + + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(HaveLen(1)) + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].Name).To(Equal("cache-mount-secret-" + testSecretName1)) + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].MountPath).To(Equal("/etc/fluid/secrets/" + testSecretName1)) + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].ReadOnly).To(BeTrue()) + }) + }) + + Context("when dataset has mount-specific encrypt options", func() { + It("should correctly transform mount encrypt options to master volumes", func() { + dataset := &datav1alpha1.Dataset{ + Spec: datav1alpha1.DatasetSpec{ + Mounts: []datav1alpha1.Mount{ + { + MountPoint: testMountPoint, + Name: testMountName, + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "aws-secret-access-key", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: testSecretName2, + Key: testSecretKey, + }, + }, + }, + }, + }, + }, + }, + } + + engine.transformEncryptOptionsToComponentVolumes(dataset, value.Master) + + Expect(value.Master.PodTemplateSpec.Spec.Volumes).To(HaveLen(1)) + Expect(value.Master.PodTemplateSpec.Spec.Volumes[0].Name).To(Equal("cache-mount-secret-" + testSecretName2)) + Expect(value.Master.PodTemplateSpec.Spec.Volumes[0].Secret.SecretName).To(Equal(testSecretName2)) + + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(HaveLen(1)) + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].Name).To(Equal("cache-mount-secret-" + testSecretName2)) + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].MountPath).To(Equal("/etc/fluid/secrets/" + testSecretName2)) + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].ReadOnly).To(BeTrue()) + }) + }) + + Context("when dataset has both shared and mount-specific encrypt options", func() { + It("should correctly transform all encrypt options to master volumes", func() { + dataset := &datav1alpha1.Dataset{ + Spec: datav1alpha1.DatasetSpec{ + Mounts: []datav1alpha1.Mount{ + { + MountPoint: testMountPoint, + Name: testMountName, + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "aws-secret-access-key", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: testSecretName2, + Key: testSecretKey, + }, + }, + }, + }, + }, + }, + SharedEncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "aws-access-key-id", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: testSecretName1, + Key: testSecretKey, + }, + }, + }, + }, + }, + } + + engine.transformEncryptOptionsToComponentVolumes(dataset, value.Master) + + Expect(value.Master.PodTemplateSpec.Spec.Volumes).To(HaveLen(2)) + volumeNames := []string{ + value.Master.PodTemplateSpec.Spec.Volumes[0].Name, + value.Master.PodTemplateSpec.Spec.Volumes[1].Name, + } + Expect(volumeNames).To(ContainElements( + "cache-mount-secret-"+testSecretName1, + "cache-mount-secret-"+testSecretName2, + )) + + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(HaveLen(2)) + mountNames := []string{ + value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].Name, + value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[1].Name, + } + Expect(mountNames).To(ContainElements( + "cache-mount-secret-"+testSecretName1, + "cache-mount-secret-"+testSecretName2, + )) + }) + }) + + Context("when dataset has native fluid scheme mount", func() { + It("should skip native fluid scheme mounts", func() { + dataset := &datav1alpha1.Dataset{ + Spec: datav1alpha1.DatasetSpec{ + Mounts: []datav1alpha1.Mount{ + { + MountPoint: nativeMountPoint, + Name: testMountName, + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "some-option", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: testSecretName1, + Key: testSecretKey, + }, + }, + }, + }, + }, + }, + }, + } + + engine.transformEncryptOptionsToComponentVolumes(dataset, value.Master) + + Expect(value.Master.PodTemplateSpec.Spec.Volumes).To(BeEmpty()) + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(BeEmpty()) + }) + }) + + Context("when master is disabled", func() { + It("should not add any volumes", func() { + value.Master.Enabled = false + dataset := &datav1alpha1.Dataset{ + Spec: datav1alpha1.DatasetSpec{ + Mounts: []datav1alpha1.Mount{ + { + MountPoint: testMountPoint, + Name: testMountName, + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "some-option", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: testSecretName1, + Key: testSecretKey, + }, + }, + }, + }, + }, + }, + }, + } + + engine.transformEncryptOptionsToComponentVolumes(dataset, value.Master) + + Expect(value.Master.PodTemplateSpec.Spec.Volumes).To(BeEmpty()) + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(BeEmpty()) + }) + }) + + Context("when master is nil", func() { + It("should not panic", func() { + value.Master = nil + dataset := &datav1alpha1.Dataset{ + Spec: datav1alpha1.DatasetSpec{ + Mounts: []datav1alpha1.Mount{ + { + MountPoint: testMountPoint, + Name: testMountName, + }, + }, + }, + } + + // Should not panic + engine.transformEncryptOptionsToComponentVolumes(dataset, value.Master) + }) + }) + + Context("when same secret is used multiple times", func() { + It("should override existing volume and volume mount", func() { + dataset := &datav1alpha1.Dataset{ + Spec: datav1alpha1.DatasetSpec{ + Mounts: []datav1alpha1.Mount{ + { + MountPoint: testMountPoint, + Name: testMountName, + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "option1", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: testSecretName1, + Key: testSecretKey, + }, + }, + }, + }, + }, + { + MountPoint: "s3://another-bucket", + Name: "another-mount", + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "option2", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: testSecretName1, + Key: testSecretKey, + }, + }, + }, + }, + }, + }, + }, + } + + engine.transformEncryptOptionsToComponentVolumes(dataset, value.Master) + + // Should only have one volume for the same secret + Expect(value.Master.PodTemplateSpec.Spec.Volumes).To(HaveLen(1)) + Expect(value.Master.PodTemplateSpec.Spec.Volumes[0].Name).To(Equal("cache-mount-secret-" + testSecretName1)) + + // Should only have one volume mount for the same secret + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(HaveLen(1)) + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].Name).To(Equal("cache-mount-secret-" + testSecretName1)) + }) + }) + }) + + Describe("transformEncryptOptionsToComponentVolumes for Worker", func() { + BeforeEach(func() { + value.Worker = &common.CacheRuntimeComponentValue{ + Enabled: true, + PodTemplateSpec: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "worker", + }, + }, + }, + }, + } + }) + + Context("when dataset has shared encrypt options for worker", func() { + It("should correctly transform shared encrypt options to worker volumes", func() { + dataset := &datav1alpha1.Dataset{ + Spec: datav1alpha1.DatasetSpec{ + Mounts: []datav1alpha1.Mount{ + { + MountPoint: testMountPoint, + Name: testMountName, + }, + }, + SharedEncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "aws-access-key-id", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: testSecretName1, + Key: testSecretKey, + }, + }, + }, + }, + }, + } + + engine.transformEncryptOptionsToComponentVolumes(dataset, value.Worker) + + Expect(value.Worker.PodTemplateSpec.Spec.Volumes).To(HaveLen(1)) + Expect(value.Worker.PodTemplateSpec.Spec.Volumes[0].Name).To(Equal("cache-mount-secret-" + testSecretName1)) + Expect(value.Worker.PodTemplateSpec.Spec.Volumes[0].Secret.SecretName).To(Equal(testSecretName1)) + + Expect(value.Worker.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(HaveLen(1)) + Expect(value.Worker.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].Name).To(Equal("cache-mount-secret-" + testSecretName1)) + Expect(value.Worker.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].MountPath).To(Equal("/etc/fluid/secrets/" + testSecretName1)) + Expect(value.Worker.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].ReadOnly).To(BeTrue()) + }) + }) + + Context("when worker is disabled", func() { + It("should not add any volumes", func() { + value.Worker.Enabled = false + dataset := &datav1alpha1.Dataset{ + Spec: datav1alpha1.DatasetSpec{ + Mounts: []datav1alpha1.Mount{ + { + MountPoint: testMountPoint, + Name: testMountName, + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "some-option", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: testSecretName1, + Key: testSecretKey, + }, + }, + }, + }, + }, + }, + }, + } + + engine.transformEncryptOptionsToComponentVolumes(dataset, value.Worker) + + Expect(value.Worker.PodTemplateSpec.Spec.Volumes).To(BeEmpty()) + Expect(value.Worker.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(BeEmpty()) + }) + }) + }) +}) diff --git a/pkg/ddc/cache/engine/transform_worker.go b/pkg/ddc/cache/engine/transform_worker.go index 6671c7949fc..eb07e1661e9 100644 --- a/pkg/ddc/cache/engine/transform_worker.go +++ b/pkg/ddc/cache/engine/transform_worker.go @@ -21,7 +21,7 @@ import ( "github.com/fluid-cloudnative/fluid/pkg/common" ) -func (e *CacheEngine) transformWorker(runtime *datav1alpha1.CacheRuntime, runtimeClass *datav1alpha1.CacheRuntimeClass, +func (e *CacheEngine) transformWorker(dataset *datav1alpha1.Dataset, runtime *datav1alpha1.CacheRuntime, runtimeClass *datav1alpha1.CacheRuntimeClass, config *CacheRuntimeComponentCommonConfig, value *common.CacheRuntimeValue) error { if runtimeClass.Topology.Worker == nil || runtime.Spec.Worker.Disabled { @@ -51,6 +51,9 @@ func (e *CacheEngine) transformWorker(runtime *datav1alpha1.CacheRuntime, runtim return err } + // transform encrypt options to worker volumes + e.transformEncryptOptionsToComponentVolumes(dataset, value.Worker) + // TODO: transform runtime.Spec.Worker, runtimeClass.Topology.Worker, dataset.Spec into PodTemplateSpec return nil diff --git a/pkg/ddc/cache/engine/ufs.go b/pkg/ddc/cache/engine/ufs.go index cac88ec42ec..32730ba36a8 100644 --- a/pkg/ddc/cache/engine/ufs.go +++ b/pkg/ddc/cache/engine/ufs.go @@ -17,14 +17,14 @@ package engine import ( + "time" + datav1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1" "github.com/fluid-cloudnative/fluid/pkg/common" - "time" ) -func (e *CacheEngine) PrepareUFS(entries *datav1alpha1.ExecutionEntries, value *common.CacheRuntimeValue) error { +func (e *CacheEngine) PrepareUFS(mountUfs *datav1alpha1.ExecutionCommonEntry, value *common.CacheRuntimeValue) error { // execute mount command in master pod - mountUfs := entries.MountUFS if mountUfs == nil { return nil } diff --git a/pkg/ddc/cache/engine/util.go b/pkg/ddc/cache/engine/util.go index ff95d183cad..2287a742b94 100644 --- a/pkg/ddc/cache/engine/util.go +++ b/pkg/ddc/cache/engine/util.go @@ -73,3 +73,18 @@ func (e *CacheEngine) getRuntimeConfigFileName() string { func (e *CacheEngine) getRuntimeClassExtraConfigMapVolumeName(name string) string { return fmt.Sprintf("fluid-extra-%s-%s", e.name, name) } + +// getSecretVolumeName generates the volume name for a secret mount +func getSecretVolumeName(secretName string) string { + return fmt.Sprintf("cache-mount-secret-%s", secretName) +} + +// getSecretMountPath generates the base mount path for a secret in the container +func getSecretMountPath(secretName string) string { + return fmt.Sprintf("/etc/fluid/secrets/%s", secretName) +} + +// getSecretFilePath generates the full file path for a secret key in the container +func getSecretFilePath(secretName, secretKey string) string { + return fmt.Sprintf("%s/%s", getSecretMountPath(secretName), secretKey) +} diff --git a/test/gha-e2e/curvine/dataset.yaml b/test/gha-e2e/curvine/dataset.yaml index e6f7ebe0969..f589741305e 100644 --- a/test/gha-e2e/curvine/dataset.yaml +++ b/test/gha-e2e/curvine/dataset.yaml @@ -1,11 +1,11 @@ -# apiVersion: v1 -# kind: Secret -# metadata: -# name: curvine-secret -# stringData: -# access-key: minioadmin -# secret-key: minioadmin -# --- +apiVersion: v1 +kind: Secret +metadata: + name: curvine-secret +stringData: + access-key: minioadmin + secret-key: minioadmin +--- apiVersion: data.fluid.io/v1alpha1 kind: Dataset metadata: @@ -26,5 +26,14 @@ spec: endpoint_url: "http://minio:9000" region_name: "us-east-1" path_style: "true" - access: "minioadmin" - secret: "minioadmin" + encryptOptions: + - name: access + valueFrom: + secretKeyRef: + name: curvine-secret + key: access-key + - name: secret + valueFrom: + secretKeyRef: + name: curvine-secret + key: secret-key diff --git a/test/gha-e2e/curvine/mount.yaml b/test/gha-e2e/curvine/mount.yaml index 093978af2c3..506a09a0a02 100644 --- a/test/gha-e2e/curvine/mount.yaml +++ b/test/gha-e2e/curvine/mount.yaml @@ -36,8 +36,27 @@ data: [ -z "$mountPoint" ] && { echo "path is not set or empty"; exit 1; } # ==================== 字段提取加p 仅输出匹配结果 ==================== - access=$(echo "$item" | sed -nE 's/.*"access":"([^"]+)".*/\1/p') - secret=$(echo "$item" | sed -nE 's/.*"secret":"([^"]+)".*/\1/p') + # Extract encryptOptions paths from the JSON + encryptOptions_raw=$(echo "$item" | sed -nE 's/.*"encryptOptions":\{([^}]+)\}.*/\1/p') + + # Extract access and secret file paths from encryptOptions + access_path="" + secret_path="" + if [ -n "$encryptOptions_raw" ]; then + access_path=$(echo "$encryptOptions_raw" | sed -nE 's/.*"access":"([^"]+)".*/\1/p') + secret_path=$(echo "$encryptOptions_raw" | sed -nE 's/.*"secret":"([^"]+)".*/\1/p') + fi + + # Read actual values from secret files if paths are provided + access="" + secret="" + if [ -n "$access_path" ] && [ -f "$access_path" ]; then + access=$(cat "$access_path") + fi + if [ -n "$secret_path" ] && [ -f "$secret_path" ]; then + secret=$(cat "$secret_path") + fi + endpoint=$(echo "$item" | sed -nE 's/.*"endpoint_url":"([^"]+)".*/\1/p') region=$(echo "$item" | sed -nE 's/.*"region_name":"([^"]+)".*/\1/p') path_style=$(echo "$item" | sed -nE 's/.*"path_style":"([^"]+)".*/\1/p') From db4ff252f00bab44419e864d1e6abf9ce6a17c81 Mon Sep 17 00:00:00 2001 From: xliuqq Date: Mon, 27 Apr 2026 10:33:11 +0800 Subject: [PATCH 2/9] fix openapi Signed-off-by: xliuqq --- api/v1alpha1/openapi_generated.go | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/api/v1alpha1/openapi_generated.go b/api/v1alpha1/openapi_generated.go index 7c4fd6dc490..e9e81dd84ee 100644 --- a/api/v1alpha1/openapi_generated.go +++ b/api/v1alpha1/openapi_generated.go @@ -79,7 +79,6 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/fluid-cloudnative/fluid/api/v1alpha1.EFCRuntimeList": schema_fluid_cloudnative_fluid_api_v1alpha1_EFCRuntimeList(ref), "github.com/fluid-cloudnative/fluid/api/v1alpha1.EFCRuntimeSpec": schema_fluid_cloudnative_fluid_api_v1alpha1_EFCRuntimeSpec(ref), "github.com/fluid-cloudnative/fluid/api/v1alpha1.EncryptOption": schema_fluid_cloudnative_fluid_api_v1alpha1_EncryptOption(ref), - "github.com/fluid-cloudnative/fluid/api/v1alpha1.EncryptOptionComponentDependency": schema_fluid_cloudnative_fluid_api_v1alpha1_EncryptOptionComponentDependency(ref), "github.com/fluid-cloudnative/fluid/api/v1alpha1.EncryptOptionSource": schema_fluid_cloudnative_fluid_api_v1alpha1_EncryptOptionSource(ref), "github.com/fluid-cloudnative/fluid/api/v1alpha1.ExecutionCommonEntry": schema_fluid_cloudnative_fluid_api_v1alpha1_ExecutionCommonEntry(ref), "github.com/fluid-cloudnative/fluid/api/v1alpha1.ExecutionEntries": schema_fluid_cloudnative_fluid_api_v1alpha1_ExecutionEntries(ref), @@ -3790,17 +3789,6 @@ func schema_fluid_cloudnative_fluid_api_v1alpha1_EncryptOption(ref common.Refere } } -func schema_fluid_cloudnative_fluid_api_v1alpha1_EncryptOptionComponentDependency(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "EncryptOptionComponentDependency defines the configuration for encrypt option dependency", - Type: []string{"object"}, - }, - }, - } -} - func schema_fluid_cloudnative_fluid_api_v1alpha1_EncryptOptionSource(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -6823,12 +6811,6 @@ func schema_fluid_cloudnative_fluid_api_v1alpha1_RuntimeComponentDependencies(re Description: "RuntimeComponentDependencies defines the dependencies required by a CacheRuntime component", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "encryptOption": { - SchemaProps: spec.SchemaProps{ - Description: "EncryptOption is the configuration for encrypt option secret mount", - Ref: ref("github.com/fluid-cloudnative/fluid/api/v1alpha1.EncryptOptionComponentDependency"), - }, - }, "extraResources": { SchemaProps: spec.SchemaProps{ Description: "ExtraResources specifies the usage of extra resources such as ConfigMaps", @@ -6839,7 +6821,7 @@ func schema_fluid_cloudnative_fluid_api_v1alpha1_RuntimeComponentDependencies(re }, }, Dependencies: []string{ - "github.com/fluid-cloudnative/fluid/api/v1alpha1.EncryptOptionComponentDependency", "github.com/fluid-cloudnative/fluid/api/v1alpha1.ExtraResourcesComponentDependency"}, + "github.com/fluid-cloudnative/fluid/api/v1alpha1.ExtraResourcesComponentDependency"}, } } From c2a960933915f2f2fbc649930e7fbec79e89e133 Mon Sep 17 00:00:00 2001 From: xliuqq Date: Mon, 27 Apr 2026 21:20:56 +0800 Subject: [PATCH 3/9] remove extra parameters Signed-off-by: xliuqq --- pkg/ddc/cache/engine/dataset.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/ddc/cache/engine/dataset.go b/pkg/ddc/cache/engine/dataset.go index e3756ec1f6e..59213b945b4 100644 --- a/pkg/ddc/cache/engine/dataset.go +++ b/pkg/ddc/cache/engine/dataset.go @@ -114,11 +114,11 @@ func (e *CacheEngine) generateDatasetMountOptions(m *datav1alpha1.Mount, sharedE // collect encrypt options, mount options will overwrite shared options. encryptOptions := map[string]string{} var err error - err = e.collectEncryptOptions(sharedEncryptOptions, encryptOptions, m.Name) + err = e.collectEncryptOptions(sharedEncryptOptions, encryptOptions) if err != nil { return mOptions, encryptOptions, err } - err = e.collectEncryptOptions(m.EncryptOptions, encryptOptions, m.Name) + err = e.collectEncryptOptions(m.EncryptOptions, encryptOptions) if err != nil { return mOptions, encryptOptions, err } @@ -126,8 +126,7 @@ func (e *CacheEngine) generateDatasetMountOptions(m *datav1alpha1.Mount, sharedE return mOptions, encryptOptions, nil } -func (e *CacheEngine) collectEncryptOptions(encryptOpts []datav1alpha1.EncryptOption, - existingEncryptOpts map[string]string, mountName string) error { +func (e *CacheEngine) collectEncryptOptions(encryptOpts []datav1alpha1.EncryptOption, existingEncryptOpts map[string]string) error { for _, item := range encryptOpts { sRef := item.ValueFrom.SecretKeyRef From 2a1ce26bf8a7a1e653e083974047eabef00d96e6 Mon Sep 17 00:00:00 2001 From: xliuqq Date: Wed, 29 Apr 2026 17:10:03 +0800 Subject: [PATCH 4/9] improve transformEncryptOptionsToComponentVolumes implemetation Signed-off-by: xliuqq --- pkg/ddc/cache/engine/transform_volumes.go | 55 +++++++++++++---------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/pkg/ddc/cache/engine/transform_volumes.go b/pkg/ddc/cache/engine/transform_volumes.go index 20420b58b73..8d234faa808 100644 --- a/pkg/ddc/cache/engine/transform_volumes.go +++ b/pkg/ddc/cache/engine/transform_volumes.go @@ -26,36 +26,45 @@ import ( // transformEncryptOptionsToComponentVolumes transforms encrypt options from dataset spec to component pod volumes // This function can be reused for both Master and Worker components func (e *CacheEngine) transformEncryptOptionsToComponentVolumes(dataset *datav1alpha1.Dataset, component *common.CacheRuntimeComponentValue) { - if component == nil || !component.Enabled { + if component == nil || !component.Enabled || len(component.PodTemplateSpec.Spec.Containers) == 0 { return } + // Helper to add secret volume and mount to the component + addSecret := func(secretName string) { + volName := getSecretVolumeName(secretName) + volumeToAdd := corev1.Volume{ + Name: volName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: secretName, + }, + }, + } + component.PodTemplateSpec.Spec.Volumes = utils.AppendOrOverrideVolume( + component.PodTemplateSpec.Spec.Volumes, volumeToAdd) + + volumeMountToAdd := corev1.VolumeMount{ + Name: volName, + ReadOnly: true, + MountPath: getSecretMountPath(secretName), + } + component.PodTemplateSpec.Spec.Containers[0].VolumeMounts = utils.AppendOrOverrideVolumeMounts( + component.PodTemplateSpec.Spec.Containers[0].VolumeMounts, volumeMountToAdd) + } + + // 1. Process shared encrypt options once + for _, encryptOpt := range dataset.Spec.SharedEncryptOptions { + addSecret(encryptOpt.ValueFrom.SecretKeyRef.Name) + } + + // 2. Process mount-specific encrypt options, override shared options for _, m := range dataset.Spec.Mounts { if common.IsFluidNativeScheme(m.MountPoint) { continue } - for _, encryptOpt := range append(dataset.Spec.SharedEncryptOptions, m.EncryptOptions...) { - secretName := encryptOpt.ValueFrom.SecretKeyRef.Name - - volName := getSecretVolumeName(secretName) - volumeToAdd := corev1.Volume{ - Name: volName, - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: secretName, - }, - }, - } - component.PodTemplateSpec.Spec.Volumes = utils.AppendOrOverrideVolume( - component.PodTemplateSpec.Spec.Volumes, volumeToAdd) - - volumeMountToAdd := corev1.VolumeMount{ - Name: volName, - ReadOnly: true, - MountPath: getSecretMountPath(secretName), - } - component.PodTemplateSpec.Spec.Containers[0].VolumeMounts = utils.AppendOrOverrideVolumeMounts( - component.PodTemplateSpec.Spec.Containers[0].VolumeMounts, volumeMountToAdd) + for _, encryptOpt := range m.EncryptOptions { + addSecret(encryptOpt.ValueFrom.SecretKeyRef.Name) } } } From f00e37bf73d763c66e1f1d7c8c5e99e9564969da Mon Sep 17 00:00:00 2001 From: xliuqq Date: Thu, 30 Apr 2026 14:16:42 +0800 Subject: [PATCH 5/9] not return error Signed-off-by: xliuqq --- pkg/ddc/cache/engine/cm.go | 5 +---- pkg/ddc/cache/engine/dataset.go | 19 +++++-------------- test/gha-e2e/curvine/mount.yaml | 2 +- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/pkg/ddc/cache/engine/cm.go b/pkg/ddc/cache/engine/cm.go index 1f03abb6a69..be35d1f8b5f 100644 --- a/pkg/ddc/cache/engine/cm.go +++ b/pkg/ddc/cache/engine/cm.go @@ -118,10 +118,7 @@ func (e *CacheEngine) generateRuntimeConfigData(runtime *datav1alpha1.CacheRunti Shared: m.Shared, Path: m.Path, } - options, encryptOptions, err := e.generateDatasetMountOptions(&m, dataset.Spec.SharedEncryptOptions, dataset.Spec.SharedOptions) - if err != nil { - return nil, err - } + options, encryptOptions := e.generateDatasetMountOptions(&m, dataset.Spec.SharedEncryptOptions, dataset.Spec.SharedOptions) mountCg.Options = options mountCg.EncryptOptions = encryptOptions diff --git a/pkg/ddc/cache/engine/dataset.go b/pkg/ddc/cache/engine/dataset.go index 59213b945b4..6dad0263ee4 100644 --- a/pkg/ddc/cache/engine/dataset.go +++ b/pkg/ddc/cache/engine/dataset.go @@ -100,7 +100,7 @@ func (e *CacheEngine) UpdateDatasetStatus(phase datav1alpha1.DatasetPhase) (err } func (e *CacheEngine) generateDatasetMountOptions(m *datav1alpha1.Mount, sharedEncryptOptions []datav1alpha1.EncryptOption, - sharedOptions map[string]string) (map[string]string, map[string]string, error) { + sharedOptions map[string]string) (map[string]string, map[string]string) { // initialize mount options, mount options will overwrite shared options. mOptions := map[string]string{} @@ -113,20 +113,13 @@ func (e *CacheEngine) generateDatasetMountOptions(m *datav1alpha1.Mount, sharedE // collect encrypt options, mount options will overwrite shared options. encryptOptions := map[string]string{} - var err error - err = e.collectEncryptOptions(sharedEncryptOptions, encryptOptions) - if err != nil { - return mOptions, encryptOptions, err - } - err = e.collectEncryptOptions(m.EncryptOptions, encryptOptions) - if err != nil { - return mOptions, encryptOptions, err - } + e.collectEncryptOptions(sharedEncryptOptions, encryptOptions) + e.collectEncryptOptions(m.EncryptOptions, encryptOptions) - return mOptions, encryptOptions, nil + return mOptions, encryptOptions } -func (e *CacheEngine) collectEncryptOptions(encryptOpts []datav1alpha1.EncryptOption, existingEncryptOpts map[string]string) error { +func (e *CacheEngine) collectEncryptOptions(encryptOpts []datav1alpha1.EncryptOption, existingEncryptOpts map[string]string) { for _, item := range encryptOpts { sRef := item.ValueFrom.SecretKeyRef @@ -138,6 +131,4 @@ func (e *CacheEngine) collectEncryptOptions(encryptOpts []datav1alpha1.EncryptOp // Store in map: key is option name, value is secret path existingEncryptOpts[item.Name] = secretPath } - - return nil } diff --git a/test/gha-e2e/curvine/mount.yaml b/test/gha-e2e/curvine/mount.yaml index 506a09a0a02..4cc1d7a903a 100644 --- a/test/gha-e2e/curvine/mount.yaml +++ b/test/gha-e2e/curvine/mount.yaml @@ -33,7 +33,7 @@ data: fi [ -z "$mountPoint" ] && { echo "mountPoint is not set or empty"; exit 1; } - [ -z "$mountPoint" ] && { echo "path is not set or empty"; exit 1; } + [ -z "$path" ] && { echo "path is not set or empty"; exit 1; } # ==================== 字段提取加p 仅输出匹配结果 ==================== # Extract encryptOptions paths from the JSON From bcc01feafbb906379815bfecfa66cc49d868eb28 Mon Sep 17 00:00:00 2001 From: xliuqq Date: Thu, 30 Apr 2026 20:42:48 +0800 Subject: [PATCH 6/9] fix secret volume limit and nil panic Signed-off-by: xliuqq --- pkg/ddc/cache/engine/cm.go | 5 +- pkg/ddc/cache/engine/dataset.go | 27 +- pkg/ddc/cache/engine/dataset_test.go | 402 ++++++++++++++++++ pkg/ddc/cache/engine/transform_client.go | 2 +- pkg/ddc/cache/engine/transform_master.go | 2 +- .../cache/engine/transform_volumes_test.go | 24 +- pkg/ddc/cache/engine/transform_worker.go | 2 +- pkg/ddc/cache/engine/util.go | 37 +- pkg/ddc/cache/engine/util_test.go | 196 +++++++++ 9 files changed, 672 insertions(+), 25 deletions(-) create mode 100644 pkg/ddc/cache/engine/dataset_test.go create mode 100644 pkg/ddc/cache/engine/util_test.go diff --git a/pkg/ddc/cache/engine/cm.go b/pkg/ddc/cache/engine/cm.go index be35d1f8b5f..1f03abb6a69 100644 --- a/pkg/ddc/cache/engine/cm.go +++ b/pkg/ddc/cache/engine/cm.go @@ -118,7 +118,10 @@ func (e *CacheEngine) generateRuntimeConfigData(runtime *datav1alpha1.CacheRunti Shared: m.Shared, Path: m.Path, } - options, encryptOptions := e.generateDatasetMountOptions(&m, dataset.Spec.SharedEncryptOptions, dataset.Spec.SharedOptions) + options, encryptOptions, err := e.generateDatasetMountOptions(&m, dataset.Spec.SharedEncryptOptions, dataset.Spec.SharedOptions) + if err != nil { + return nil, err + } mountCg.Options = options mountCg.EncryptOptions = encryptOptions diff --git a/pkg/ddc/cache/engine/dataset.go b/pkg/ddc/cache/engine/dataset.go index 6dad0263ee4..da707cdad7a 100644 --- a/pkg/ddc/cache/engine/dataset.go +++ b/pkg/ddc/cache/engine/dataset.go @@ -18,6 +18,7 @@ package engine import ( "context" + "fmt" "reflect" datav1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1" @@ -100,10 +101,13 @@ func (e *CacheEngine) UpdateDatasetStatus(phase datav1alpha1.DatasetPhase) (err } func (e *CacheEngine) generateDatasetMountOptions(m *datav1alpha1.Mount, sharedEncryptOptions []datav1alpha1.EncryptOption, - sharedOptions map[string]string) (map[string]string, map[string]string) { + sharedOptions map[string]string) (mOptions map[string]string, encryptOptions map[string]string, err error) { + + // Initialize return maps + mOptions = make(map[string]string) + encryptOptions = make(map[string]string) // initialize mount options, mount options will overwrite shared options. - mOptions := map[string]string{} for k, v := range sharedOptions { mOptions[k] = v } @@ -112,17 +116,25 @@ func (e *CacheEngine) generateDatasetMountOptions(m *datav1alpha1.Mount, sharedE } // collect encrypt options, mount options will overwrite shared options. - encryptOptions := map[string]string{} - e.collectEncryptOptions(sharedEncryptOptions, encryptOptions) - e.collectEncryptOptions(m.EncryptOptions, encryptOptions) + err = e.collectEncryptOptions(sharedEncryptOptions, encryptOptions) + if err != nil { + return + } + err = e.collectEncryptOptions(m.EncryptOptions, encryptOptions) + if err != nil { + return + } - return mOptions, encryptOptions + return } -func (e *CacheEngine) collectEncryptOptions(encryptOpts []datav1alpha1.EncryptOption, existingEncryptOpts map[string]string) { +func (e *CacheEngine) collectEncryptOptions(encryptOpts []datav1alpha1.EncryptOption, existingEncryptOpts map[string]string) error { for _, item := range encryptOpts { sRef := item.ValueFrom.SecretKeyRef + if sRef.Name == "" || sRef.Key == "" { + return fmt.Errorf("encryptOption %s has empty secretKeyRef name or key", item.Name) + } // Construct the secret mount path in the container // The secret will be mounted at /etc/fluid/secrets// @@ -131,4 +143,5 @@ func (e *CacheEngine) collectEncryptOptions(encryptOpts []datav1alpha1.EncryptOp // Store in map: key is option name, value is secret path existingEncryptOpts[item.Name] = secretPath } + return nil } diff --git a/pkg/ddc/cache/engine/dataset_test.go b/pkg/ddc/cache/engine/dataset_test.go new file mode 100644 index 00000000000..5200cb8227e --- /dev/null +++ b/pkg/ddc/cache/engine/dataset_test.go @@ -0,0 +1,402 @@ +/* +Copyright 2026 The Fluid Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package engine + +import ( + datav1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("generateDatasetMountOptions Tests", Label("pkg.ddc.cache.engine.dataset_test.go"), func() { + var engine *CacheEngine + + BeforeEach(func() { + engine = &CacheEngine{} + }) + + Describe("generateDatasetMountOptions", func() { + Context("when mount has no options and no encrypt options", func() { + It("should return empty maps", func() { + mount := &datav1alpha1.Mount{ + Name: "test-mount", + MountPoint: "s3://test-bucket", + } + var sharedEncryptOptions []datav1alpha1.EncryptOption + sharedOptions := map[string]string{} + + mOptions, encryptOptions, err := engine.generateDatasetMountOptions(mount, sharedEncryptOptions, sharedOptions) + + Expect(err).NotTo(HaveOccurred()) + Expect(mOptions).To(BeEmpty()) + Expect(encryptOptions).To(BeEmpty()) + }) + }) + + Context("when mount has only options", func() { + It("should return mount options correctly", func() { + mount := &datav1alpha1.Mount{ + Name: "test-mount", + MountPoint: "s3://test-bucket", + Options: map[string]string{ + "option1": "value1", + "option2": "value2", + }, + } + var sharedEncryptOptions []datav1alpha1.EncryptOption + sharedOptions := map[string]string{} + + mOptions, encryptOptions, err := engine.generateDatasetMountOptions(mount, sharedEncryptOptions, sharedOptions) + + Expect(err).NotTo(HaveOccurred()) + Expect(mOptions).To(HaveLen(2)) + Expect(mOptions["option1"]).To(Equal("value1")) + Expect(mOptions["option2"]).To(Equal("value2")) + Expect(encryptOptions).To(BeEmpty()) + }) + }) + + Context("when shared options exist", func() { + It("should merge shared options with mount options", func() { + mount := &datav1alpha1.Mount{ + Name: "test-mount", + MountPoint: "s3://test-bucket", + Options: map[string]string{ + "mount-option": "mount-value", + }, + } + var sharedEncryptOptions []datav1alpha1.EncryptOption + sharedOptions := map[string]string{ + "shared-option": "shared-value", + } + + mOptions, encryptOptions, err := engine.generateDatasetMountOptions(mount, sharedEncryptOptions, sharedOptions) + + Expect(err).NotTo(HaveOccurred()) + Expect(mOptions).To(HaveLen(2)) + Expect(mOptions["shared-option"]).To(Equal("shared-value")) + Expect(mOptions["mount-option"]).To(Equal("mount-value")) + Expect(encryptOptions).To(BeEmpty()) + }) + + It("mount options should override shared options with same key", func() { + mount := &datav1alpha1.Mount{ + Name: "test-mount", + MountPoint: "s3://test-bucket", + Options: map[string]string{ + "common-option": "mount-value", + }, + } + var sharedEncryptOptions []datav1alpha1.EncryptOption + sharedOptions := map[string]string{ + "common-option": "shared-value", + } + + mOptions, encryptOptions, err := engine.generateDatasetMountOptions(mount, sharedEncryptOptions, sharedOptions) + + Expect(err).NotTo(HaveOccurred()) + Expect(mOptions).To(HaveLen(1)) + // Mount options should override shared options + Expect(mOptions["common-option"]).To(Equal("mount-value")) + Expect(encryptOptions).To(BeEmpty()) + }) + }) + + Context("when shared encrypt options exist", func() { + It("should collect shared encrypt options correctly", func() { + mount := &datav1alpha1.Mount{ + Name: "test-mount", + MountPoint: "s3://test-bucket", + } + sharedEncryptOptions := []datav1alpha1.EncryptOption{ + { + Name: "aws-access-key-id", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "aws-secret", + Key: "access-key", + }, + }, + }, + } + sharedOptions := map[string]string{} + + mOptions, encryptOptions, err := engine.generateDatasetMountOptions(mount, sharedEncryptOptions, sharedOptions) + + Expect(err).NotTo(HaveOccurred()) + Expect(mOptions).To(BeEmpty()) + Expect(encryptOptions).To(HaveLen(1)) + Expect(encryptOptions["aws-access-key-id"]).To(Equal("/etc/fluid/secrets/aws-secret/access-key")) + }) + }) + + Context("when mount has encrypt options", func() { + It("should collect mount encrypt options correctly", func() { + mount := &datav1alpha1.Mount{ + Name: "test-mount", + MountPoint: "s3://test-bucket", + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "aws-secret-access-key", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "aws-secret", + Key: "secret-key", + }, + }, + }, + }, + } + var sharedEncryptOptions []datav1alpha1.EncryptOption + sharedOptions := map[string]string{} + + mOptions, encryptOptions, err := engine.generateDatasetMountOptions(mount, sharedEncryptOptions, sharedOptions) + + Expect(err).NotTo(HaveOccurred()) + Expect(mOptions).To(BeEmpty()) + Expect(encryptOptions).To(HaveLen(1)) + Expect(encryptOptions["aws-secret-access-key"]).To(Equal("/etc/fluid/secrets/aws-secret/secret-key")) + }) + }) + + Context("when both shared and mount encrypt options exist", func() { + It("should collect all encrypt options", func() { + mount := &datav1alpha1.Mount{ + Name: "test-mount", + MountPoint: "s3://test-bucket", + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "mount-encrypt-option", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "mount-secret", + Key: "key1", + }, + }, + }, + }, + } + sharedEncryptOptions := []datav1alpha1.EncryptOption{ + { + Name: "shared-encrypt-option", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "shared-secret", + Key: "key2", + }, + }, + }, + } + sharedOptions := map[string]string{} + + mOptions, encryptOptions, err := engine.generateDatasetMountOptions(mount, sharedEncryptOptions, sharedOptions) + + Expect(err).NotTo(HaveOccurred()) + Expect(mOptions).To(BeEmpty()) + Expect(encryptOptions).To(HaveLen(2)) + Expect(encryptOptions["shared-encrypt-option"]).To(Equal("/etc/fluid/secrets/shared-secret/key2")) + Expect(encryptOptions["mount-encrypt-option"]).To(Equal("/etc/fluid/secrets/mount-secret/key1")) + }) + + It("mount encrypt options should override shared encrypt options with same name", func() { + mount := &datav1alpha1.Mount{ + Name: "test-mount", + MountPoint: "s3://test-bucket", + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "common-encrypt-option", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "mount-secret", + Key: "mount-key", + }, + }, + }, + }, + } + sharedEncryptOptions := []datav1alpha1.EncryptOption{ + { + Name: "common-encrypt-option", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "shared-secret", + Key: "shared-key", + }, + }, + }, + } + sharedOptions := map[string]string{} + + mOptions, encryptOptions, err := engine.generateDatasetMountOptions(mount, sharedEncryptOptions, sharedOptions) + + Expect(err).NotTo(HaveOccurred()) + Expect(mOptions).To(BeEmpty()) + Expect(encryptOptions).To(HaveLen(1)) + // Mount encrypt options should override shared encrypt options + Expect(encryptOptions["common-encrypt-option"]).To(Equal("/etc/fluid/secrets/mount-secret/mount-key")) + }) + }) + + Context("when encrypt option has empty secret key ref", func() { + It("should return error when secret name is empty", func() { + mount := &datav1alpha1.Mount{ + Name: "test-mount", + MountPoint: "s3://test-bucket", + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "test-option", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "", + Key: "some-key", + }, + }, + }, + }, + } + var sharedEncryptOptions []datav1alpha1.EncryptOption + sharedOptions := map[string]string{} + + mOptions, encryptOptions, err := engine.generateDatasetMountOptions(mount, sharedEncryptOptions, sharedOptions) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("empty secretKeyRef name or key")) + Expect(mOptions).To(BeEmpty()) + Expect(encryptOptions).To(BeEmpty()) + }) + + It("should return error when secret key is empty", func() { + mount := &datav1alpha1.Mount{ + Name: "test-mount", + MountPoint: "s3://test-bucket", + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "test-option", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "some-secret", + Key: "", + }, + }, + }, + }, + } + var sharedEncryptOptions []datav1alpha1.EncryptOption + sharedOptions := map[string]string{} + + mOptions, encryptOptions, err := engine.generateDatasetMountOptions(mount, sharedEncryptOptions, sharedOptions) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("empty secretKeyRef name or key")) + Expect(mOptions).To(BeEmpty()) + Expect(encryptOptions).To(BeEmpty()) + }) + }) + + Context("when complex scenario with all options", func() { + It("should handle all types of options together", func() { + mount := &datav1alpha1.Mount{ + Name: "test-mount", + MountPoint: "s3://test-bucket", + Options: map[string]string{ + "mount-opt1": "mount-val1", + "mount-opt2": "mount-val2", + }, + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "mount-encrypt", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "mount-secret", + Key: "mount-key", + }, + }, + }, + }, + } + sharedEncryptOptions := []datav1alpha1.EncryptOption{ + { + Name: "shared-encrypt", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "shared-secret", + Key: "shared-key", + }, + }, + }, + } + sharedOptions := map[string]string{ + "shared-opt1": "shared-val1", + "mount-opt1": "shared-val-override", + } + + mOptions, encryptOptions, err := engine.generateDatasetMountOptions(mount, sharedEncryptOptions, sharedOptions) + + Expect(err).NotTo(HaveOccurred()) + // Check mount options: 3 total (2 from mount + 1 from shared, but mount-opt1 overridden by mount) + Expect(mOptions).To(HaveLen(3)) + Expect(mOptions["shared-opt1"]).To(Equal("shared-val1")) + Expect(mOptions["mount-opt1"]).To(Equal("mount-val1")) // Mount overrides shared + Expect(mOptions["mount-opt2"]).To(Equal("mount-val2")) + + // Check encrypt options: 2 total + Expect(encryptOptions).To(HaveLen(2)) + Expect(encryptOptions["shared-encrypt"]).To(Equal("/etc/fluid/secrets/shared-secret/shared-key")) + Expect(encryptOptions["mount-encrypt"]).To(Equal("/etc/fluid/secrets/mount-secret/mount-key")) + }) + }) + + Context("when multiple encrypt options reference same secret", func() { + It("should generate correct paths for each option", func() { + mount := &datav1alpha1.Mount{ + Name: "test-mount", + MountPoint: "s3://test-bucket", + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "option1", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "same-secret", + Key: "key1", + }, + }, + }, + { + Name: "option2", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "same-secret", + Key: "key2", + }, + }, + }, + }, + } + var sharedEncryptOptions []datav1alpha1.EncryptOption + sharedOptions := map[string]string{} + + mOptions, encryptOptions, err := engine.generateDatasetMountOptions(mount, sharedEncryptOptions, sharedOptions) + + Expect(err).NotTo(HaveOccurred()) + Expect(mOptions).To(BeEmpty()) + Expect(encryptOptions).To(HaveLen(2)) + Expect(encryptOptions["option1"]).To(Equal("/etc/fluid/secrets/same-secret/key1")) + Expect(encryptOptions["option2"]).To(Equal("/etc/fluid/secrets/same-secret/key2")) + }) + }) + }) +}) diff --git a/pkg/ddc/cache/engine/transform_client.go b/pkg/ddc/cache/engine/transform_client.go index 35305148535..5c33fda560f 100644 --- a/pkg/ddc/cache/engine/transform_client.go +++ b/pkg/ddc/cache/engine/transform_client.go @@ -26,7 +26,7 @@ func (e *CacheEngine) transformClient(runtime *datav1alpha1.CacheRuntime, runtim config *CacheRuntimeComponentCommonConfig, value *common.CacheRuntimeValue) error { if runtimeClass.Topology.Client == nil || runtime.Spec.Client.Disabled { - value.Client.Enabled = false + value.Client = &common.CacheRuntimeComponentValue{Enabled: false} return nil } diff --git a/pkg/ddc/cache/engine/transform_master.go b/pkg/ddc/cache/engine/transform_master.go index a91e199e8b3..3392bbe4af4 100644 --- a/pkg/ddc/cache/engine/transform_master.go +++ b/pkg/ddc/cache/engine/transform_master.go @@ -25,7 +25,7 @@ func (e *CacheEngine) transformMaster(dataset *datav1alpha1.Dataset, runtime *da config *CacheRuntimeComponentCommonConfig, value *common.CacheRuntimeValue) error { // TODO: these two field both indicate Master enabled or not, should be combined into one field. if runtimeClass.Topology.Master == nil || runtime.Spec.Master.Disabled { - value.Master.Enabled = false + value.Master = &common.CacheRuntimeComponentValue{Enabled: false} return nil } diff --git a/pkg/ddc/cache/engine/transform_volumes_test.go b/pkg/ddc/cache/engine/transform_volumes_test.go index 612df09e48b..0a616b47dc4 100644 --- a/pkg/ddc/cache/engine/transform_volumes_test.go +++ b/pkg/ddc/cache/engine/transform_volumes_test.go @@ -87,11 +87,11 @@ var _ = Describe("CacheEngine Transform Volumes Tests", Label("pkg.ddc.cache.eng engine.transformEncryptOptionsToComponentVolumes(dataset, value.Master) Expect(value.Master.PodTemplateSpec.Spec.Volumes).To(HaveLen(1)) - Expect(value.Master.PodTemplateSpec.Spec.Volumes[0].Name).To(Equal("cache-mount-secret-" + testSecretName1)) + Expect(value.Master.PodTemplateSpec.Spec.Volumes[0].Name).To(Equal(secretVolumeNamePrefix + testSecretName1)) Expect(value.Master.PodTemplateSpec.Spec.Volumes[0].Secret.SecretName).To(Equal(testSecretName1)) Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(HaveLen(1)) - Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].Name).To(Equal("cache-mount-secret-" + testSecretName1)) + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].Name).To(Equal(secretVolumeNamePrefix + testSecretName1)) Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].MountPath).To(Equal("/etc/fluid/secrets/" + testSecretName1)) Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].ReadOnly).To(BeTrue()) }) @@ -124,11 +124,11 @@ var _ = Describe("CacheEngine Transform Volumes Tests", Label("pkg.ddc.cache.eng engine.transformEncryptOptionsToComponentVolumes(dataset, value.Master) Expect(value.Master.PodTemplateSpec.Spec.Volumes).To(HaveLen(1)) - Expect(value.Master.PodTemplateSpec.Spec.Volumes[0].Name).To(Equal("cache-mount-secret-" + testSecretName2)) + Expect(value.Master.PodTemplateSpec.Spec.Volumes[0].Name).To(Equal(secretVolumeNamePrefix + testSecretName2)) Expect(value.Master.PodTemplateSpec.Spec.Volumes[0].Secret.SecretName).To(Equal(testSecretName2)) Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(HaveLen(1)) - Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].Name).To(Equal("cache-mount-secret-" + testSecretName2)) + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].Name).To(Equal(secretVolumeNamePrefix + testSecretName2)) Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].MountPath).To(Equal("/etc/fluid/secrets/" + testSecretName2)) Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].ReadOnly).To(BeTrue()) }) @@ -177,8 +177,8 @@ var _ = Describe("CacheEngine Transform Volumes Tests", Label("pkg.ddc.cache.eng value.Master.PodTemplateSpec.Spec.Volumes[1].Name, } Expect(volumeNames).To(ContainElements( - "cache-mount-secret-"+testSecretName1, - "cache-mount-secret-"+testSecretName2, + secretVolumeNamePrefix+testSecretName1, + secretVolumeNamePrefix+testSecretName2, )) Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(HaveLen(2)) @@ -187,8 +187,8 @@ var _ = Describe("CacheEngine Transform Volumes Tests", Label("pkg.ddc.cache.eng value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[1].Name, } Expect(mountNames).To(ContainElements( - "cache-mount-secret-"+testSecretName1, - "cache-mount-secret-"+testSecretName2, + secretVolumeNamePrefix+testSecretName1, + secretVolumeNamePrefix+testSecretName2, )) }) }) @@ -318,11 +318,11 @@ var _ = Describe("CacheEngine Transform Volumes Tests", Label("pkg.ddc.cache.eng // Should only have one volume for the same secret Expect(value.Master.PodTemplateSpec.Spec.Volumes).To(HaveLen(1)) - Expect(value.Master.PodTemplateSpec.Spec.Volumes[0].Name).To(Equal("cache-mount-secret-" + testSecretName1)) + Expect(value.Master.PodTemplateSpec.Spec.Volumes[0].Name).To(Equal(secretVolumeNamePrefix + testSecretName1)) // Should only have one volume mount for the same secret Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(HaveLen(1)) - Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].Name).To(Equal("cache-mount-secret-" + testSecretName1)) + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].Name).To(Equal(secretVolumeNamePrefix + testSecretName1)) }) }) }) @@ -370,11 +370,11 @@ var _ = Describe("CacheEngine Transform Volumes Tests", Label("pkg.ddc.cache.eng engine.transformEncryptOptionsToComponentVolumes(dataset, value.Worker) Expect(value.Worker.PodTemplateSpec.Spec.Volumes).To(HaveLen(1)) - Expect(value.Worker.PodTemplateSpec.Spec.Volumes[0].Name).To(Equal("cache-mount-secret-" + testSecretName1)) + Expect(value.Worker.PodTemplateSpec.Spec.Volumes[0].Name).To(Equal(secretVolumeNamePrefix + testSecretName1)) Expect(value.Worker.PodTemplateSpec.Spec.Volumes[0].Secret.SecretName).To(Equal(testSecretName1)) Expect(value.Worker.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(HaveLen(1)) - Expect(value.Worker.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].Name).To(Equal("cache-mount-secret-" + testSecretName1)) + Expect(value.Worker.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].Name).To(Equal(secretVolumeNamePrefix + testSecretName1)) Expect(value.Worker.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].MountPath).To(Equal("/etc/fluid/secrets/" + testSecretName1)) Expect(value.Worker.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].ReadOnly).To(BeTrue()) }) diff --git a/pkg/ddc/cache/engine/transform_worker.go b/pkg/ddc/cache/engine/transform_worker.go index eb07e1661e9..e6a71547871 100644 --- a/pkg/ddc/cache/engine/transform_worker.go +++ b/pkg/ddc/cache/engine/transform_worker.go @@ -25,7 +25,7 @@ func (e *CacheEngine) transformWorker(dataset *datav1alpha1.Dataset, runtime *da config *CacheRuntimeComponentCommonConfig, value *common.CacheRuntimeValue) error { if runtimeClass.Topology.Worker == nil || runtime.Spec.Worker.Disabled { - value.Worker.Enabled = false + value.Worker = &common.CacheRuntimeComponentValue{Enabled: false} return nil } diff --git a/pkg/ddc/cache/engine/util.go b/pkg/ddc/cache/engine/util.go index 2287a742b94..f199ca45051 100644 --- a/pkg/ddc/cache/engine/util.go +++ b/pkg/ddc/cache/engine/util.go @@ -17,9 +17,22 @@ package engine import ( + "crypto/sha256" + "encoding/hex" "fmt" + "github.com/fluid-cloudnative/fluid/pkg/common" "github.com/fluid-cloudnative/fluid/pkg/utils" + "k8s.io/apimachinery/pkg/util/validation" +) + +// Precomputed max lengths for the 63-char DNS limit +const ( + secretVolumeNamePrefix = "cache-mnt-secret-" + secretMaxTotalLength = validation.DNS1035LabelMaxLength + prefixSecretVolumeLength = len(secretVolumeNamePrefix) + hashSuffixLength = 8 + truncatedSecretMaxLength = secretMaxTotalLength - prefixSecretVolumeLength - hashSuffixLength ) // GetComponentName gets the component name using runtime name and component type. @@ -75,8 +88,28 @@ func (e *CacheEngine) getRuntimeClassExtraConfigMapVolumeName(name string) strin } // getSecretVolumeName generates the volume name for a secret mount -func getSecretVolumeName(secretName string) string { - return fmt.Sprintf("cache-mount-secret-%s", secretName) +func getSecretVolumeName(name string) string { + fullName := fmt.Sprintf("%s%s", secretVolumeNamePrefix, name) + // check volume name length + if len(fullName) <= validation.DNS1035LabelMaxLength { + return fullName + } + + // Case 2: Long name - truncate + hash (fallback) + // Step 1: Truncate secret to 36 chars + truncatedName := name + if len(truncatedName) > truncatedSecretMaxLength { + truncatedName = truncatedName[:truncatedSecretMaxLength] + } + + // Step 2: Generate 8-char SHA-256 hash of the ORIGINAL secret name (prevents collisions) + hash := sha256.Sum256([]byte(name)) + shortHash := hex.EncodeToString(hash[:])[:hashSuffixLength] + + // Step 3: Combine to exact 63 chars + volumeName := secretVolumeNamePrefix + truncatedName + shortHash + + return volumeName } // getSecretMountPath generates the base mount path for a secret in the container diff --git a/pkg/ddc/cache/engine/util_test.go b/pkg/ddc/cache/engine/util_test.go new file mode 100644 index 00000000000..440f0ce69f2 --- /dev/null +++ b/pkg/ddc/cache/engine/util_test.go @@ -0,0 +1,196 @@ +/* +Copyright 2026 The Fluid Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package engine + +import ( + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/util/validation" +) + +var _ = Describe("getSecretVolumeName Tests", Label("pkg.ddc.cache.engine.util_test.go"), func() { + Describe("getSecretVolumeName", func() { + Context("when secret name is short", func() { + It("should return prefix + secretName without truncation", func() { + secretName := "test-secret" + result := getSecretVolumeName(secretName) + expected := secretVolumeNamePrefix + secretName + + Expect(result).To(Equal(expected)) + Expect(len(result)).To(BeNumerically("<=", validation.DNS1035LabelMaxLength)) + }) + + It("should handle empty secret name", func() { + secretName := "" + result := getSecretVolumeName(secretName) + expected := secretVolumeNamePrefix + + Expect(result).To(Equal(expected)) + Expect(len(result)).To(BeNumerically("<=", validation.DNS1035LabelMaxLength)) + }) + + It("should handle single character secret name", func() { + secretName := "a" + result := getSecretVolumeName(secretName) + expected := secretVolumeNamePrefix + "a" + + Expect(result).To(Equal(expected)) + Expect(len(result)).To(BeNumerically("<=", validation.DNS1035LabelMaxLength)) + }) + }) + + Context("when secret name is at the boundary", func() { + It("should not truncate when total length equals DNS1035LabelMaxLength", func() { + // Calculate max secret name length that fits exactly + maxSecretLen := validation.DNS1035LabelMaxLength - prefixSecretVolumeLength + secretName := strings.Repeat("a", maxSecretLen) + result := getSecretVolumeName(secretName) + expected := secretVolumeNamePrefix + secretName + + Expect(result).To(Equal(expected)) + Expect(len(result)).To(Equal(validation.DNS1035LabelMaxLength)) + }) + + It("should truncate when total length exceeds DNS1035LabelMaxLength by 1", func() { + // Calculate secret name length that exceeds by 1 + maxSecretLen := validation.DNS1035LabelMaxLength - prefixSecretVolumeLength + 1 + secretName := strings.Repeat("a", maxSecretLen) + result := getSecretVolumeName(secretName) + + Expect(len(result)).To(BeNumerically("<=", validation.DNS1035LabelMaxLength)) + Expect(result).To(HavePrefix(secretVolumeNamePrefix)) + // Should have hash suffix + Expect(len(result)).To(Equal(validation.DNS1035LabelMaxLength)) + }) + }) + + Context("when secret name is very long", func() { + It("should truncate and add hash suffix", func() { + secretName := strings.Repeat("very-long-secret-name-", 10) // 240 chars + result := getSecretVolumeName(secretName) + + Expect(len(result)).To(BeNumerically("<=", validation.DNS1035LabelMaxLength)) + Expect(result).To(HavePrefix(secretVolumeNamePrefix)) + // Should end with 8-char hash + Expect(len(result)).To(Equal(validation.DNS1035LabelMaxLength)) + + // Verify hash part is present (last 8 chars) + hashPart := result[len(result)-hashSuffixLength:] + Expect(hashPart).To(MatchRegexp("^[0-9a-f]{8}$")) + }) + + It("should generate different hashes for different secret names with same prefix", func() { + secretName1 := strings.Repeat("a", 100) + "suffix1" + secretName2 := strings.Repeat("a", 100) + "suffix2" + + result1 := getSecretVolumeName(secretName1) + result2 := getSecretVolumeName(secretName2) + + Expect(result1).NotTo(Equal(result2)) + // Both should be valid DNS names + Expect(len(result1)).To(BeNumerically("<=", validation.DNS1035LabelMaxLength)) + Expect(len(result2)).To(BeNumerically("<=", validation.DNS1035LabelMaxLength)) + }) + + It("should handle extremely long secret names", func() { + secretName := strings.Repeat("x", 1000) + result := getSecretVolumeName(secretName) + + Expect(len(result)).To(BeNumerically("<=", validation.DNS1035LabelMaxLength)) + Expect(result).To(HavePrefix(secretVolumeNamePrefix)) + Expect(len(result)).To(Equal(validation.DNS1035LabelMaxLength)) + }) + }) + + Context("when secret name contains special characters", func() { + It("should handle secret names with hyphens", func() { + secretName := "my-test-secret-name" + result := getSecretVolumeName(secretName) + expected := secretVolumeNamePrefix + secretName + + Expect(result).To(Equal(expected)) + }) + + It("should handle secret names with numbers", func() { + secretName := "secret123" + result := getSecretVolumeName(secretName) + expected := secretVolumeNamePrefix + secretName + + Expect(result).To(Equal(expected)) + }) + + It("should handle long secret names with special characters", func() { + secretName := strings.Repeat("test-secret-123-", 10) + result := getSecretVolumeName(secretName) + + Expect(len(result)).To(BeNumerically("<=", validation.DNS1035LabelMaxLength)) + Expect(result).To(HavePrefix(secretVolumeNamePrefix)) + }) + }) + + Context("DNS label validation", func() { + It("should always generate valid DNS-1035 label names", func() { + testCases := []string{ + "short", + "medium-length-secret-name", + strings.Repeat("a", 50), + strings.Repeat("b", 100), + strings.Repeat("c", 200), + "test-with-hyphens-and-numbers-123", + } + + for _, secretName := range testCases { + result := getSecretVolumeName(secretName) + + // Check length constraint + Expect(len(result)).To(BeNumerically("<=", validation.DNS1035LabelMaxLength), + "Result length %d exceeds max %d for secret: %s", + len(result), validation.DNS1035LabelMaxLength, secretName) + + // Check starts with letter + Expect(result[0]).To(Satisfy(func(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') + }), "Result should start with a letter for secret: %s", secretName) + + // Check only contains lowercase letters, numbers, and hyphens + Expect(result).To(MatchRegexp("^[a-z0-9-]+$"), + "Result contains invalid characters for secret: %s", secretName) + } + }) + }) + + Context("collision prevention", func() { + It("should generate unique volume names for different secrets that truncate to same prefix", func() { + // Two different secrets that would truncate to the same prefix + secretName1 := strings.Repeat("a", truncatedSecretMaxLength) + "different1" + secretName2 := strings.Repeat("a", truncatedSecretMaxLength) + "different2" + + result1 := getSecretVolumeName(secretName1) + result2 := getSecretVolumeName(secretName2) + + // Should be different due to hash + Expect(result1).NotTo(Equal(result2)) + + // Both should be valid length + Expect(len(result1)).To(Equal(validation.DNS1035LabelMaxLength)) + Expect(len(result2)).To(Equal(validation.DNS1035LabelMaxLength)) + }) + }) + }) +}) From 4d13abccc076fff0053ebce39cc9d5feb230dc1f Mon Sep 17 00:00:00 2001 From: xliuqq Date: Mon, 4 May 2026 22:32:49 +0800 Subject: [PATCH 7/9] fix nil panic Signed-off-by: xliuqq --- pkg/ddc/cache/engine/setup.go | 3 +- pkg/ddc/cache/engine/transform.go | 6 +- pkg/ddc/cache/engine/transform_client.go | 2 +- pkg/ddc/cache/engine/transform_master.go | 2 +- pkg/ddc/cache/engine/transform_volumes.go | 3 + .../cache/engine/transform_volumes_test.go | 160 ++++++++++++++++++ pkg/ddc/cache/engine/transform_worker.go | 2 +- 7 files changed, 172 insertions(+), 6 deletions(-) diff --git a/pkg/ddc/cache/engine/setup.go b/pkg/ddc/cache/engine/setup.go index 2a9053ffb48..3c7ff1ff45e 100644 --- a/pkg/ddc/cache/engine/setup.go +++ b/pkg/ddc/cache/engine/setup.go @@ -86,7 +86,8 @@ func (e *CacheEngine) Setup(ctx cruntime.ReconcileRequestContext) (ready bool, e } // dataset mount after runtime ready to ensure master pod is ready for executing commands. - if runtimeValue.Master.Enabled && runtimeClass.Topology.Master.ExecutionEntries != nil { + if runtimeValue.Master.Enabled && runtimeClass.Topology != nil && + runtimeClass.Topology.Master != nil && runtimeClass.Topology.Master.ExecutionEntries != nil { // currently only support mount ufs for master in master-worker architecture err = e.PrepareUFS(runtimeClass.Topology.Master.ExecutionEntries.MountUFS, runtimeValue) if err != nil { diff --git a/pkg/ddc/cache/engine/transform.go b/pkg/ddc/cache/engine/transform.go index 28014fd9bf0..419e56f6abc 100644 --- a/pkg/ddc/cache/engine/transform.go +++ b/pkg/ddc/cache/engine/transform.go @@ -19,12 +19,13 @@ package engine import ( "errors" "fmt" + "time" + datav1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1" "github.com/fluid-cloudnative/fluid/pkg/common" "github.com/fluid-cloudnative/fluid/pkg/utils" "github.com/fluid-cloudnative/fluid/pkg/utils/transformer" corev1 "k8s.io/api/core/v1" - "time" ) // CacheRuntimeComponentCommonConfig common config for transform @@ -52,7 +53,8 @@ type RuntimeConfigVolumeConfig struct { func (e *CacheEngine) transform(dataset *datav1alpha1.Dataset, runtime *datav1alpha1.CacheRuntime, runtimeClass *datav1alpha1.CacheRuntimeClass) (*common.CacheRuntimeValue, error) { - if runtimeClass.Topology.Master == nil && runtimeClass.Topology.Worker == nil && runtimeClass.Topology.Client == nil { + if runtimeClass.Topology == nil || + (runtimeClass.Topology.Master == nil && runtimeClass.Topology.Worker == nil && runtimeClass.Topology.Client == nil) { return nil, fmt.Errorf("at least one component should be defined in runtimeClass") } defer utils.TimeTrack(time.Now(), "CacheRuntime.transform", "name", runtime.Name) diff --git a/pkg/ddc/cache/engine/transform_client.go b/pkg/ddc/cache/engine/transform_client.go index 5c33fda560f..1c5a2689df7 100644 --- a/pkg/ddc/cache/engine/transform_client.go +++ b/pkg/ddc/cache/engine/transform_client.go @@ -25,7 +25,7 @@ import ( func (e *CacheEngine) transformClient(runtime *datav1alpha1.CacheRuntime, runtimeClass *datav1alpha1.CacheRuntimeClass, config *CacheRuntimeComponentCommonConfig, value *common.CacheRuntimeValue) error { - if runtimeClass.Topology.Client == nil || runtime.Spec.Client.Disabled { + if runtimeClass.Topology == nil || runtimeClass.Topology.Client == nil || runtime.Spec.Client.Disabled { value.Client = &common.CacheRuntimeComponentValue{Enabled: false} return nil } diff --git a/pkg/ddc/cache/engine/transform_master.go b/pkg/ddc/cache/engine/transform_master.go index 3392bbe4af4..bdd630003a0 100644 --- a/pkg/ddc/cache/engine/transform_master.go +++ b/pkg/ddc/cache/engine/transform_master.go @@ -24,7 +24,7 @@ import ( func (e *CacheEngine) transformMaster(dataset *datav1alpha1.Dataset, runtime *datav1alpha1.CacheRuntime, runtimeClass *datav1alpha1.CacheRuntimeClass, config *CacheRuntimeComponentCommonConfig, value *common.CacheRuntimeValue) error { // TODO: these two field both indicate Master enabled or not, should be combined into one field. - if runtimeClass.Topology.Master == nil || runtime.Spec.Master.Disabled { + if runtimeClass.Topology == nil || runtimeClass.Topology.Master == nil || runtime.Spec.Master.Disabled { value.Master = &common.CacheRuntimeComponentValue{Enabled: false} return nil } diff --git a/pkg/ddc/cache/engine/transform_volumes.go b/pkg/ddc/cache/engine/transform_volumes.go index 8d234faa808..340470418b7 100644 --- a/pkg/ddc/cache/engine/transform_volumes.go +++ b/pkg/ddc/cache/engine/transform_volumes.go @@ -32,6 +32,9 @@ func (e *CacheEngine) transformEncryptOptionsToComponentVolumes(dataset *datav1a // Helper to add secret volume and mount to the component addSecret := func(secretName string) { + if secretName == "" { + return + } volName := getSecretVolumeName(secretName) volumeToAdd := corev1.Volume{ Name: volName, diff --git a/pkg/ddc/cache/engine/transform_volumes_test.go b/pkg/ddc/cache/engine/transform_volumes_test.go index 0a616b47dc4..0e407d41e5f 100644 --- a/pkg/ddc/cache/engine/transform_volumes_test.go +++ b/pkg/ddc/cache/engine/transform_volumes_test.go @@ -325,6 +325,123 @@ var _ = Describe("CacheEngine Transform Volumes Tests", Label("pkg.ddc.cache.eng Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].Name).To(Equal(secretVolumeNamePrefix + testSecretName1)) }) }) + + Context("when encrypt option has empty secret name", func() { + It("should skip encrypt options with empty secret name in shared encrypt options", func() { + dataset := &datav1alpha1.Dataset{ + Spec: datav1alpha1.DatasetSpec{ + Mounts: []datav1alpha1.Mount{ + { + MountPoint: testMountPoint, + Name: testMountName, + }, + }, + SharedEncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "aws-access-key-id", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "", // Empty secret name + Key: testSecretKey, + }, + }, + }, + }, + }, + } + + engine.transformEncryptOptionsToComponentVolumes(dataset, value.Master) + + // Should not add any volumes for empty secret name + Expect(value.Master.PodTemplateSpec.Spec.Volumes).To(BeEmpty()) + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(BeEmpty()) + }) + + It("should skip encrypt options with empty secret name in mount encrypt options", func() { + dataset := &datav1alpha1.Dataset{ + Spec: datav1alpha1.DatasetSpec{ + Mounts: []datav1alpha1.Mount{ + { + MountPoint: testMountPoint, + Name: testMountName, + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "aws-secret-access-key", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "", // Empty secret name + Key: testSecretKey, + }, + }, + }, + }, + }, + }, + }, + } + + engine.transformEncryptOptionsToComponentVolumes(dataset, value.Master) + + // Should not add any volumes for empty secret name + Expect(value.Master.PodTemplateSpec.Spec.Volumes).To(BeEmpty()) + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(BeEmpty()) + }) + + It("should skip empty secret names but process valid ones", func() { + dataset := &datav1alpha1.Dataset{ + Spec: datav1alpha1.DatasetSpec{ + Mounts: []datav1alpha1.Mount{ + { + MountPoint: testMountPoint, + Name: testMountName, + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "invalid-option", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "", // Empty secret name - should be skipped + Key: testSecretKey, + }, + }, + }, + { + Name: "valid-option", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: testSecretName1, // Valid secret name + Key: testSecretKey, + }, + }, + }, + }, + }, + }, + SharedEncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "another-invalid-option", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "", // Empty secret name - should be skipped + Key: testSecretKey, + }, + }, + }, + }, + }, + } + + engine.transformEncryptOptionsToComponentVolumes(dataset, value.Master) + + // Should only add volume for the valid secret name + Expect(value.Master.PodTemplateSpec.Spec.Volumes).To(HaveLen(1)) + Expect(value.Master.PodTemplateSpec.Spec.Volumes[0].Name).To(Equal(secretVolumeNamePrefix + testSecretName1)) + Expect(value.Master.PodTemplateSpec.Spec.Volumes[0].Secret.SecretName).To(Equal(testSecretName1)) + + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(HaveLen(1)) + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].Name).To(Equal(secretVolumeNamePrefix + testSecretName1)) + Expect(value.Master.PodTemplateSpec.Spec.Containers[0].VolumeMounts[0].MountPath).To(Equal("/etc/fluid/secrets/" + testSecretName1)) + }) + }) }) Describe("transformEncryptOptionsToComponentVolumes for Worker", func() { @@ -411,5 +528,48 @@ var _ = Describe("CacheEngine Transform Volumes Tests", Label("pkg.ddc.cache.eng Expect(value.Worker.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(BeEmpty()) }) }) + + Context("when encrypt option has empty secret name for worker", func() { + It("should skip encrypt options with empty secret name", func() { + dataset := &datav1alpha1.Dataset{ + Spec: datav1alpha1.DatasetSpec{ + Mounts: []datav1alpha1.Mount{ + { + MountPoint: testMountPoint, + Name: testMountName, + EncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "aws-secret-access-key", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "", // Empty secret name + Key: testSecretKey, + }, + }, + }, + }, + }, + }, + SharedEncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "aws-access-key-id", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: "", // Empty secret name + Key: testSecretKey, + }, + }, + }, + }, + }, + } + + engine.transformEncryptOptionsToComponentVolumes(dataset, value.Worker) + + // Should not add any volumes for empty secret name + Expect(value.Worker.PodTemplateSpec.Spec.Volumes).To(BeEmpty()) + Expect(value.Worker.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(BeEmpty()) + }) + }) }) }) diff --git a/pkg/ddc/cache/engine/transform_worker.go b/pkg/ddc/cache/engine/transform_worker.go index e6a71547871..eb6a3f2ea9b 100644 --- a/pkg/ddc/cache/engine/transform_worker.go +++ b/pkg/ddc/cache/engine/transform_worker.go @@ -24,7 +24,7 @@ import ( func (e *CacheEngine) transformWorker(dataset *datav1alpha1.Dataset, runtime *datav1alpha1.CacheRuntime, runtimeClass *datav1alpha1.CacheRuntimeClass, config *CacheRuntimeComponentCommonConfig, value *common.CacheRuntimeValue) error { - if runtimeClass.Topology.Worker == nil || runtime.Spec.Worker.Disabled { + if runtimeClass.Topology == nil || runtimeClass.Topology.Worker == nil || runtime.Spec.Worker.Disabled { value.Worker = &common.CacheRuntimeComponentValue{Enabled: false} return nil } From 9e063a1387dd72da51149080793077df8bfa0a79 Mon Sep 17 00:00:00 2001 From: xliuqq Date: Tue, 5 May 2026 23:20:04 +0800 Subject: [PATCH 8/9] component secret mount should be configurable per runtime implementation. Signed-off-by: xliuqq --- api/v1alpha1/cacheruntimeclass_types.go | 12 ++ api/v1alpha1/zz_generated.deepcopy.go | 20 ++++ .../data.fluid.io_cacheruntimeclasses.yaml | 15 +++ .../data.fluid.io_cacheruntimeclasses.yaml | 15 +++ pkg/ddc/cache/engine/transform.go | 2 +- pkg/ddc/cache/engine/transform_client.go | 7 +- pkg/ddc/cache/engine/transform_master.go | 6 +- pkg/ddc/cache/engine/transform_volumes.go | 10 ++ .../cache/engine/transform_volumes_test.go | 109 ++++++++++++++++++ pkg/ddc/cache/engine/transform_worker.go | 6 +- test/gha-e2e/curvine/cacheruntimeclass.yaml | 3 + 11 files changed, 199 insertions(+), 6 deletions(-) diff --git a/api/v1alpha1/cacheruntimeclass_types.go b/api/v1alpha1/cacheruntimeclass_types.go index c1b56d6ca37..c2e2b225cf8 100644 --- a/api/v1alpha1/cacheruntimeclass_types.go +++ b/api/v1alpha1/cacheruntimeclass_types.go @@ -90,11 +90,23 @@ type ExtraResourcesComponentDependency struct { // RuntimeComponentDependencies defines the dependencies required by a CacheRuntime component type RuntimeComponentDependencies struct { + // SecretMount controls whether dataset encrypt-option secrets are mounted into this component pod. + // Defaults to true for Master/Worker, false for Client unless explicitly enabled. + // +optional + SecretMount *SecretMountComponentDependency `json:"secretMount,omitempty"` + // ExtraResources specifies the usage of extra resources such as ConfigMaps // +optional ExtraResources *ExtraResourcesComponentDependency `json:"extraResources,omitempty"` } +// SecretMountComponentDependency defines the secret mount configuration for component dependencies +type SecretMountComponentDependency struct { + // Enabled indicates whether dataset encrypt-option secrets should be mounted into this component pod. + // +optional + Enabled bool `json:"enabled,omitempty"` +} + // HeadlessRuntimeComponentService defines the configuration for headless service type HeadlessRuntimeComponentService struct { } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index ef5e9798c1e..e6f7214ad7b 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -2905,6 +2905,11 @@ func (in *RuntimeComponentDefinition) DeepCopy() *RuntimeComponentDefinition { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RuntimeComponentDependencies) DeepCopyInto(out *RuntimeComponentDependencies) { *out = *in + if in.SecretMount != nil { + in, out := &in.SecretMount, &out.SecretMount + *out = new(SecretMountComponentDependency) + **out = **in + } if in.ExtraResources != nil { in, out := &in.ExtraResources, &out.ExtraResources *out = new(ExtraResourcesComponentDependency) @@ -3215,6 +3220,21 @@ func (in *SecretKeySelector) DeepCopy() *SecretKeySelector { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretMountComponentDependency) DeepCopyInto(out *SecretMountComponentDependency) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretMountComponentDependency. +func (in *SecretMountComponentDependency) DeepCopy() *SecretMountComponentDependency { + if in == nil { + return nil + } + out := new(SecretMountComponentDependency) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TargetDataset) DeepCopyInto(out *TargetDataset) { *out = *in diff --git a/charts/fluid/fluid/crds/data.fluid.io_cacheruntimeclasses.yaml b/charts/fluid/fluid/crds/data.fluid.io_cacheruntimeclasses.yaml index be2cc842ac1..4af3480c5e0 100644 --- a/charts/fluid/fluid/crds/data.fluid.io_cacheruntimeclasses.yaml +++ b/charts/fluid/fluid/crds/data.fluid.io_cacheruntimeclasses.yaml @@ -84,6 +84,11 @@ spec: type: object type: array type: object + secretMount: + properties: + enabled: + type: boolean + type: object type: object executionEntries: properties: @@ -3499,6 +3504,11 @@ spec: type: object type: array type: object + secretMount: + properties: + enabled: + type: boolean + type: object type: object executionEntries: properties: @@ -6914,6 +6924,11 @@ spec: type: object type: array type: object + secretMount: + properties: + enabled: + type: boolean + type: object type: object executionEntries: properties: diff --git a/config/crd/bases/data.fluid.io_cacheruntimeclasses.yaml b/config/crd/bases/data.fluid.io_cacheruntimeclasses.yaml index be2cc842ac1..4af3480c5e0 100644 --- a/config/crd/bases/data.fluid.io_cacheruntimeclasses.yaml +++ b/config/crd/bases/data.fluid.io_cacheruntimeclasses.yaml @@ -84,6 +84,11 @@ spec: type: object type: array type: object + secretMount: + properties: + enabled: + type: boolean + type: object type: object executionEntries: properties: @@ -3499,6 +3504,11 @@ spec: type: object type: array type: object + secretMount: + properties: + enabled: + type: boolean + type: object type: object executionEntries: properties: @@ -6914,6 +6924,11 @@ spec: type: object type: array type: object + secretMount: + properties: + enabled: + type: boolean + type: object type: object executionEntries: properties: diff --git a/pkg/ddc/cache/engine/transform.go b/pkg/ddc/cache/engine/transform.go index 419e56f6abc..67cf72f6ade 100644 --- a/pkg/ddc/cache/engine/transform.go +++ b/pkg/ddc/cache/engine/transform.go @@ -81,7 +81,7 @@ func (e *CacheEngine) transform(dataset *datav1alpha1.Dataset, runtime *datav1al if err != nil { return nil, err } - err = e.transformClient(runtime, runtimeClass, runtimeCommonConfig, runtimeValue) + err = e.transformClient(dataset, runtime, runtimeClass, runtimeCommonConfig, runtimeValue) if err != nil { return nil, err } diff --git a/pkg/ddc/cache/engine/transform_client.go b/pkg/ddc/cache/engine/transform_client.go index 1c5a2689df7..9c5dea48a58 100644 --- a/pkg/ddc/cache/engine/transform_client.go +++ b/pkg/ddc/cache/engine/transform_client.go @@ -22,7 +22,7 @@ import ( corev1 "k8s.io/api/core/v1" ) -func (e *CacheEngine) transformClient(runtime *datav1alpha1.CacheRuntime, runtimeClass *datav1alpha1.CacheRuntimeClass, +func (e *CacheEngine) transformClient(dataset *datav1alpha1.Dataset, runtime *datav1alpha1.CacheRuntime, runtimeClass *datav1alpha1.CacheRuntimeClass, config *CacheRuntimeComponentCommonConfig, value *common.CacheRuntimeValue) error { if runtimeClass.Topology == nil || runtimeClass.Topology.Client == nil || runtime.Spec.Client.Disabled { @@ -52,6 +52,11 @@ func (e *CacheEngine) transformClient(runtime *datav1alpha1.CacheRuntime, runtim return err } + // transform encrypt options to client volumes (default disabled for Client) + if shouldMountSecrets(component.Dependencies.SecretMount, false) { + e.transformEncryptOptionsToComponentVolumes(dataset, value.Client) + } + podTemplateSpec := &value.Client.PodTemplateSpec // TODO: transform runtime.Spec.Client, runtimeClass.Topology.Client, dataset.Spec into PodTemplateSpec diff --git a/pkg/ddc/cache/engine/transform_master.go b/pkg/ddc/cache/engine/transform_master.go index bdd630003a0..34e961eb85e 100644 --- a/pkg/ddc/cache/engine/transform_master.go +++ b/pkg/ddc/cache/engine/transform_master.go @@ -51,8 +51,10 @@ func (e *CacheEngine) transformMaster(dataset *datav1alpha1.Dataset, runtime *da return err } - // transform encrypt options to master volumes - e.transformEncryptOptionsToComponentVolumes(dataset, value.Master) + // transform encrypt options to master volumes (default enabled for Master) + if shouldMountSecrets(component.Dependencies.SecretMount, true) { + e.transformEncryptOptionsToComponentVolumes(dataset, value.Master) + } // TODO: transform runtime.Spec.Master, runtimeClass.Topology.Master, dataset.Spec into PodTemplateSpec diff --git a/pkg/ddc/cache/engine/transform_volumes.go b/pkg/ddc/cache/engine/transform_volumes.go index 340470418b7..25987a17294 100644 --- a/pkg/ddc/cache/engine/transform_volumes.go +++ b/pkg/ddc/cache/engine/transform_volumes.go @@ -71,3 +71,13 @@ func (e *CacheEngine) transformEncryptOptionsToComponentVolumes(dataset *datav1a } } } + +// shouldMountSecrets determines whether secrets should be mounted based on configuration and default behavior +// config: the SecretMount configuration from CacheRuntimeClass (can be nil) +// defaultEnabled: the default behavior when config is nil or not explicitly set +func shouldMountSecrets(config *datav1alpha1.SecretMountComponentDependency, defaultEnabled bool) bool { + if config == nil { + return defaultEnabled + } + return config.Enabled +} diff --git a/pkg/ddc/cache/engine/transform_volumes_test.go b/pkg/ddc/cache/engine/transform_volumes_test.go index 0e407d41e5f..1dd05308f19 100644 --- a/pkg/ddc/cache/engine/transform_volumes_test.go +++ b/pkg/ddc/cache/engine/transform_volumes_test.go @@ -572,4 +572,113 @@ var _ = Describe("CacheEngine Transform Volumes Tests", Label("pkg.ddc.cache.eng }) }) }) + + Describe("shouldMountSecrets helper function", func() { + Context("when SecretMount config is nil", func() { + It("should return defaultEnabled value", func() { + // Test with defaultEnabled = true (for Master/Worker) + Expect(shouldMountSecrets(nil, true)).To(BeTrue()) + + // Test with defaultEnabled = false (for Client) + Expect(shouldMountSecrets(nil, false)).To(BeFalse()) + }) + }) + + Context("when SecretMount config is provided", func() { + It("should return the configured Enabled value", func() { + // Test with Enabled = true + config := &datav1alpha1.SecretMountComponentDependency{ + Enabled: true, + } + Expect(shouldMountSecrets(config, false)).To(BeTrue()) + + // Test with Enabled = false + config.Enabled = false + Expect(shouldMountSecrets(config, true)).To(BeFalse()) + }) + }) + }) + + Describe("Client component secret mount behavior", func() { + var ( + clientValue *common.CacheRuntimeComponentValue + dataset *datav1alpha1.Dataset + ) + + BeforeEach(func() { + clientValue = &common.CacheRuntimeComponentValue{ + Enabled: true, + PodTemplateSpec: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "client", + }, + }, + }, + }, + } + dataset = &datav1alpha1.Dataset{ + Spec: datav1alpha1.DatasetSpec{ + SharedEncryptOptions: []datav1alpha1.EncryptOption{ + { + Name: "test-secret", + ValueFrom: datav1alpha1.EncryptOptionSource{ + SecretKeyRef: datav1alpha1.SecretKeySelector{ + Name: testSecretName1, + Key: testSecretKey, + }, + }, + }, + }, + }, + } + }) + + Context("when Client has no SecretMount configuration (default disabled)", func() { + It("should not mount secrets to client pod", func() { + // Simulate Client with nil SecretMount (default behavior) + if shouldMountSecrets(nil, false) { + engine.transformEncryptOptionsToComponentVolumes(dataset, clientValue) + } + + // Should not add any volumes for Client by default + Expect(clientValue.PodTemplateSpec.Spec.Volumes).To(BeEmpty()) + Expect(clientValue.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(BeEmpty()) + }) + }) + + Context("when Client has SecretMount explicitly enabled", func() { + It("should mount secrets to client pod", func() { + // Simulate Client with SecretMount enabled + secretMountConfig := &datav1alpha1.SecretMountComponentDependency{ + Enabled: true, + } + if shouldMountSecrets(secretMountConfig, false) { + engine.transformEncryptOptionsToComponentVolumes(dataset, clientValue) + } + + // Should add volumes for Client when explicitly enabled + Expect(clientValue.PodTemplateSpec.Spec.Volumes).To(HaveLen(1)) + Expect(clientValue.PodTemplateSpec.Spec.Volumes[0].Name).To(Equal(secretVolumeNamePrefix + testSecretName1)) + Expect(clientValue.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(HaveLen(1)) + }) + }) + + Context("when Client has SecretMount explicitly disabled", func() { + It("should not mount secrets to client pod", func() { + // Simulate Client with SecretMount explicitly disabled + secretMountConfig := &datav1alpha1.SecretMountComponentDependency{ + Enabled: false, + } + if shouldMountSecrets(secretMountConfig, false) { + engine.transformEncryptOptionsToComponentVolumes(dataset, clientValue) + } + + // Should not add any volumes for Client + Expect(clientValue.PodTemplateSpec.Spec.Volumes).To(BeEmpty()) + Expect(clientValue.PodTemplateSpec.Spec.Containers[0].VolumeMounts).To(BeEmpty()) + }) + }) + }) }) diff --git a/pkg/ddc/cache/engine/transform_worker.go b/pkg/ddc/cache/engine/transform_worker.go index eb6a3f2ea9b..418f4eea527 100644 --- a/pkg/ddc/cache/engine/transform_worker.go +++ b/pkg/ddc/cache/engine/transform_worker.go @@ -51,8 +51,10 @@ func (e *CacheEngine) transformWorker(dataset *datav1alpha1.Dataset, runtime *da return err } - // transform encrypt options to worker volumes - e.transformEncryptOptionsToComponentVolumes(dataset, value.Worker) + // transform encrypt options to worker volumes (default enabled for Worker) + if shouldMountSecrets(component.Dependencies.SecretMount, true) { + e.transformEncryptOptionsToComponentVolumes(dataset, value.Worker) + } // TODO: transform runtime.Spec.Worker, runtimeClass.Topology.Worker, dataset.Spec into PodTemplateSpec diff --git a/test/gha-e2e/curvine/cacheruntimeclass.yaml b/test/gha-e2e/curvine/cacheruntimeclass.yaml index 7bb01ddd049..e192ca94c34 100644 --- a/test/gha-e2e/curvine/cacheruntimeclass.yaml +++ b/test/gha-e2e/curvine/cacheruntimeclass.yaml @@ -170,6 +170,9 @@ topology: apiVersion: apps/v1 kind: DaemonSet dependencies: + # Uncomment to enable secret mount for client (e.g., for JuiceFS FUSE pods) + # secretMount: + # enabled: true extraResources: # 使用 extraResources 时,需要定义其挂载路径 configMaps: From 8e1ea05b911c893bcf057cc664d7d169f455c8fa Mon Sep 17 00:00:00 2001 From: xliuqq Date: Tue, 5 May 2026 23:45:41 +0800 Subject: [PATCH 9/9] fix openapi Signed-off-by: xliuqq --- api/v1alpha1/openapi_generated.go | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/api/v1alpha1/openapi_generated.go b/api/v1alpha1/openapi_generated.go index e9e81dd84ee..f7138b9488d 100644 --- a/api/v1alpha1/openapi_generated.go +++ b/api/v1alpha1/openapi_generated.go @@ -137,6 +137,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/fluid-cloudnative/fluid/api/v1alpha1.RuntimeTopology": schema_fluid_cloudnative_fluid_api_v1alpha1_RuntimeTopology(ref), "github.com/fluid-cloudnative/fluid/api/v1alpha1.ScriptProcessor": schema_fluid_cloudnative_fluid_api_v1alpha1_ScriptProcessor(ref), "github.com/fluid-cloudnative/fluid/api/v1alpha1.SecretKeySelector": schema_fluid_cloudnative_fluid_api_v1alpha1_SecretKeySelector(ref), + "github.com/fluid-cloudnative/fluid/api/v1alpha1.SecretMountComponentDependency": schema_fluid_cloudnative_fluid_api_v1alpha1_SecretMountComponentDependency(ref), "github.com/fluid-cloudnative/fluid/api/v1alpha1.TargetDataset": schema_fluid_cloudnative_fluid_api_v1alpha1_TargetDataset(ref), "github.com/fluid-cloudnative/fluid/api/v1alpha1.TargetDatasetWithMountPath": schema_fluid_cloudnative_fluid_api_v1alpha1_TargetDatasetWithMountPath(ref), "github.com/fluid-cloudnative/fluid/api/v1alpha1.TargetPath": schema_fluid_cloudnative_fluid_api_v1alpha1_TargetPath(ref), @@ -6811,6 +6812,12 @@ func schema_fluid_cloudnative_fluid_api_v1alpha1_RuntimeComponentDependencies(re Description: "RuntimeComponentDependencies defines the dependencies required by a CacheRuntime component", Type: []string{"object"}, Properties: map[string]spec.Schema{ + "secretMount": { + SchemaProps: spec.SchemaProps{ + Description: "SecretMount controls whether dataset encrypt-option secrets are mounted into this component pod. Defaults to true for Master/Worker, false for Client unless explicitly enabled.", + Ref: ref("github.com/fluid-cloudnative/fluid/api/v1alpha1.SecretMountComponentDependency"), + }, + }, "extraResources": { SchemaProps: spec.SchemaProps{ Description: "ExtraResources specifies the usage of extra resources such as ConfigMaps", @@ -6821,7 +6828,7 @@ func schema_fluid_cloudnative_fluid_api_v1alpha1_RuntimeComponentDependencies(re }, }, Dependencies: []string{ - "github.com/fluid-cloudnative/fluid/api/v1alpha1.ExtraResourcesComponentDependency"}, + "github.com/fluid-cloudnative/fluid/api/v1alpha1.ExtraResourcesComponentDependency", "github.com/fluid-cloudnative/fluid/api/v1alpha1.SecretMountComponentDependency"}, } } @@ -7589,6 +7596,26 @@ func schema_fluid_cloudnative_fluid_api_v1alpha1_SecretKeySelector(ref common.Re } } +func schema_fluid_cloudnative_fluid_api_v1alpha1_SecretMountComponentDependency(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SecretMountComponentDependency defines the secret mount configuration for component dependencies", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "enabled": { + SchemaProps: spec.SchemaProps{ + Description: "Enabled indicates whether dataset encrypt-option secrets should be mounted into this component pod.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_fluid_cloudnative_fluid_api_v1alpha1_TargetDataset(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{