Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/scripts/gha-e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,14 @@
set -e
bash test/gha-e2e/curvine/test.sh
}
function cache_selector_e2e() {

Check warning on line 107 in .github/scripts/gha-e2e.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=fluid-cloudnative_fluid&issues=AZ-n47jq8BvVdFy8n_m_&open=AZ-n47jq8BvVdFy8n_m_&pullRequest=6064
set -e
bash test/gha-e2e/cacheruntime-selector/test.sh
}

check_control_plane_status
alluxio_e2e
jindo_e2e
juicefs_e2e
curvine_e2e
cache_selector_e2e
1 change: 1 addition & 0 deletions api/v1alpha1/cacheruntime_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ type CacheRuntimeSpec struct {

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:subresource:scale:specpath=.spec.worker.replicas,statuspath=.status.worker.currentReplicas,selectorpath=.status.selector
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",priority=0
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.phase",priority=0
// +kubebuilder:resource:scope=Namespaced
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/data.fluid.io_cacheruntimes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1656,4 +1656,8 @@ spec:
served: true
storage: true
subresources:
scale:
labelSelectorPath: .status.selector
specReplicasPath: .spec.worker.replicas
statusReplicasPath: .status.worker.currentReplicas
status: {}
3 changes: 1 addition & 2 deletions pkg/ddc/cache/engine/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,7 @@ func (e *CacheEngine) setupMasterInternal(masterValue *common.CacheRuntimeCompon
runtimeToUpdate := runtime.DeepCopy()
runtimeToUpdate.Status.Master = masterStatus

// TODO(cache runtime): figure out how to use this selector
// runtimeToUpdate.Status.Selector = e.getWorkerSelectors()
runtimeToUpdate.Status.Selector = e.getWorkerSelectors()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Heads-up, out of scope for this PR but worth flagging in #6063: populating Status.Selector here is correct and useful for any tooling that reads it directly, but HPA targeting CacheRuntime/scale will still not work until the scale subresource is declared. Unlike AlluxioRuntime, JuiceFSRuntime, and VineyardRuntime, the CacheRuntime type currently only declares +kubebuilder:subresource:status — there is no +kubebuilder:subresource:scale:...selectorpath=.status.selector annotation, and the generated CRD (config/crd/bases/data.fluid.io_cacheruntimes.yaml) reflects that (only subresources: status: {}). A follow-up PR adding the scale subresource will likely be needed to fully close the linked issue.


if len(runtimeToUpdate.Status.Conditions) == 0 {
runtimeToUpdate.Status.Conditions = []datav1alpha1.RuntimeCondition{}
Expand Down
2 changes: 1 addition & 1 deletion pkg/ddc/cache/engine/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func (e *CacheEngine) CheckAndUpdateRuntimeStatus(value *common.CacheRuntimeStat
runtimeToUpdate.Status.SetupDuration = utils.CalculateDuration(runtimeToUpdate.CreationTimestamp.Time, time.Now())
}

// TODO(cache runtime): set the CacheRuntime Status left fields: Selector
runtimeToUpdate.Status.Selector = e.getWorkerSelectors()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Status.Selector = e.getWorkerSelectors() is now called both in setupMasterInternal (master.go:87) and in CheckAndUpdateRuntimeStatus (status.go:152). That is fine for correctness and matches the pattern in other engines, but it is worth a mental note: if a third status-write path ever appears, please refactor rather than adding a third copy. Not a blocker for this PR.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The selector itself looks right — getWorkerSelectors() produces cacheruntime.fluid.io/component-name=<name>-worker,cacheruntime.fluid.io/name=<name>, which matches the labels worker pods actually get from getCommonLabelsFromComponent. So this is a solid prerequisite.

One caveat worth flagging: as it stands, populating Status.Selector has no effect on HPA for CacheRuntime. The generated CRD (config/crd/bases/data.fluid.io_cacheruntimes.yaml) only has subresources: {status: {}} — there's no scale subresource with selectorpath=.status.selector, unlike AlluxioRuntime/JuiceFSRuntime/VineyardRuntime. Without that marker on the CacheRuntime type, the scale subresource doesn't exist and nothing consumes the selector, so the "worker pod discovery / auto-scaling" goal isn't reachable yet.

Could you add the +kubebuilder:subresource:scale:...,selectorpath=.status.selector marker to api/v1alpha1/cacheruntime_types.go (and regenerate the CRD) here, or open a follow-up issue to track it? Otherwise this change is a no-op for the stated benefit.

runtimeToUpdate.Status.ValueFile = common.GetCacheRuntimeConfigConfigMapName(e.name)

if !reflect.DeepEqual(runtime.Status, runtimeToUpdate.Status) {
Expand Down
13 changes: 13 additions & 0 deletions pkg/ddc/cache/engine/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/fluid-cloudnative/fluid/pkg/common"
"github.com/fluid-cloudnative/fluid/pkg/utils"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/validation"
)

Expand Down Expand Up @@ -129,3 +130,15 @@ func GetEmptyDirTieredStoreMountPath(levelIndex int) string {
func getTieredStoreMountPath(levelIndex int, pathIndex int, mediumType string) string {
return fmt.Sprintf("/etc/fluid/mount/tiered-store/level-%d-index-%d-%s", levelIndex, pathIndex, mediumType)
}

// getWorkerSelectors returns the label selector string for CacheRuntime worker pods.
// This is used to populate Status.Selector so that HPA and other tooling can
// discover worker pods, matching the pattern used by other runtimes (JindoCache,
// JuiceFS, Vineyard, EFC).
func (e *CacheEngine) getWorkerSelectors() string {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This PR still ships without an e2e test for the new behavior, and that was called out as the merge blocker last round. getWorkerSelectors is a controller-level change that flows into the CacheRuntime status subresource — unit tests cover the string-building side, but they do not exercise the actual reconcile path, the Status.Selector field on the live CR, or worker pod discovery through the scale endpoint.

Please add a focused e2e case (patterned after test/gha-e2e/curvine/, which already brings up a CacheRuntime end-to-end) that:

  1. Creates a CacheRuntime and waits for it to become ready.
  2. Reads the runtime CR and asserts .status.selector is non-empty and matches cacheruntime.fluid.io/component-name=<name>-worker,cacheruntime.fluid.io/runtime-name=<name>.
  3. Uses kubectl get --raw on the scale subresource (or kubectl get pods -l <selector>) to prove the selector actually resolves to the worker pods.

Note: the earlier review pointed at test/e2e/, but this repo uses test/gha-e2e/ for GitHub Actions kind-e2e and test/prow/ for the Python-based prow suite — either location is fine, whichever matches the runtime you target.

workerName := common.GetCacheComponentName(e.name, common.ComponentTypeWorker)
return labels.SelectorFromSet(labels.Set{
common.LabelCacheRuntimeName: e.name,
common.LabelCacheRuntimeComponentName: workerName,
}).String()
}
50 changes: 50 additions & 0 deletions pkg/ddc/cache/engine/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package engine
import (
"strings"

"github.com/fluid-cloudnative/fluid/pkg/common"
"github.com/fluid-cloudnative/fluid/pkg/utils/fake"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/util/validation"
Expand Down Expand Up @@ -194,3 +196,51 @@ var _ = Describe("getSecretVolumeName Tests", Label("pkg.ddc.cache.engine.util_t
})
})
})

var _ = Describe("getWorkerSelectors Tests", Label("pkg.ddc.cache.engine.util_test.go"), func() {
Describe("getWorkerSelectors", func() {
var engine *CacheEngine

BeforeEach(func() {
engine = &CacheEngine{
name: "test-runtime",
namespace: "default",
Log: fake.NullLogger(),
}
})

It("returns a non-empty selector string", func() {
selector := engine.getWorkerSelectors()
Expect(selector).NotTo(BeEmpty())
})

It("selector contains the runtime name label", func() {
selector := engine.getWorkerSelectors()
Expect(selector).To(ContainSubstring(common.LabelCacheRuntimeName))
Expect(selector).To(ContainSubstring("test-runtime"))
})

It("selector contains the worker component name label", func() {
workerName := common.GetCacheComponentName("test-runtime", common.ComponentTypeWorker)
selector := engine.getWorkerSelectors()
Expect(selector).To(ContainSubstring(common.LabelCacheRuntimeComponentName))
Expect(selector).To(ContainSubstring(workerName))
})

It("returns different selectors for different runtime names", func() {
engine1 := &CacheEngine{name: "runtime-a", namespace: "default", Log: fake.NullLogger()}
engine2 := &CacheEngine{name: "runtime-b", namespace: "default", Log: fake.NullLogger()}
Expect(engine1.getWorkerSelectors()).NotTo(Equal(engine2.getWorkerSelectors()))
})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Consider adding one assertion that pins the exact selector string (e.g. Expect(selector).To(Equal(...)) with the expected sorted form) so any future change to selector composition — label keys, ordering, escaping — is caught by tests. Right now the assertions only check substrings, so an unrelated label silently added in the future would not fail this test.


It("returns the exact expected selector string", func() {
// Pin the selector string so any future change to label keys, ordering,
// or escaping is caught immediately.
engine := &CacheEngine{name: "test-runtime", namespace: "default", Log: fake.NullLogger()}
workerName := common.GetCacheComponentName("test-runtime", common.ComponentTypeWorker)
expected := common.LabelCacheRuntimeComponentName + "=" + workerName +
"," + common.LabelCacheRuntimeName + "=test-runtime"
Expect(engine.getWorkerSelectors()).To(Equal(expected))
})
})
})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The new unit tests are tight — the exact-string pin in particular is a good addition. What's still missing is an e2e case exercising the field on a real CR. See the blocking issue for the concrete asks. The unit suite alone cannot detect, for example, a controller-side bug where Status.Selector gets overwritten by a later status-writer, or where the selector is set on master.go's path but not on the reconcile path.

24 changes: 24 additions & 0 deletions test/gha-e2e/cacheruntime-selector/cacheruntime.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: data.fluid.io/v1alpha1
kind: CacheRuntime
metadata:
name: selector-demo
spec:
runtimeClassName: selector-demo
master:
# 如何区分 master 和 journal 的配置,两个在一个进程中(前缀,交给Curvine自行处理)
options: # https://curvineio.github.io/zh-cn/docs/Deploy/Deploy-Curvine-Cluster/Distributed-Mode/conf#master%E9%85%8D%E7%BD%AE%E9%A1%B9
key1: master-value1
replicas: 1
worker:
options: # https://curvineio.github.io/zh-cn/docs/Deploy/Deploy-Curvine-Cluster/Distributed-Mode/conf#worker%E9%85%8D%E7%BD%AE%E9%A1%B9
key1: worker-value1
replicas: 1
tieredStore:
levels: #worker缓存配置
- low: "0.5"
high: "0.8"
emptyDir:
quota: 1Gi
client:
options:
key1: value1
210 changes: 210 additions & 0 deletions test/gha-e2e/cacheruntime-selector/cacheruntimeclass.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
apiVersion: data.fluid.io/v1alpha1
kind: CacheRuntimeClass
metadata:
name: selector-demo
fileSystemType: curvinefs
extraResources:
configMaps:
- name: curvine-config
data:
# TOML格式配置模板, 使用 hairyhenderson/gomplate 镜像要求的模板格式
cluster.toml: |
# master configuration
[master]
meta_dir = "testing/meta"

# masta ha raft configuration.
[journal]
journal_addrs = [
{{ range $index := seq 0 (sub (ds "config").master.replicas 1) }}
{{- if $index }},{{ end }}
{id = {{ add $index 1 }}, hostname = "{{(ds "config").master.name}}-{{$index}}.{{(ds "config").master.service.name}}", port = 8996}
{{- end }}
]
journal_dir = "testing/journal"

# Worker configuration, translate Gi to GB
[worker]
dir_reserved = "0"
data_dir = [
{{- range $index, $level := (ds "config").worker.tieredStoreLevels }}
{{- range $pathIndex, $mountPath := $level.mountPaths }}
{{- if or $index $pathIndex }},{{ end }}
"[{{$level.mediumType}}:{{ index $level.quotas $pathIndex | strings.ReplaceAll "i" "B"}}]{{ $mountPath }}"
{{- end }}
{{- end }}
]
dataOperationSpecs:
- name: DataLoad
command:
- "/bin/bash"
- "-c"
args:
# Actually, the cache runtime image should use $(FLUID_RUNTIME_CONFIG_PATH) to generate the config file, and
# use $(FLUID_DATALOAD_DATA_PATH) to execute data load.
- |
# currently we have no customized image supporting dataload for curvine, so we write the curvine.toml with fixed journal address for test case.
echo -e '[journal]\njournal_addrs = [\n{id=1, hostname="selector-demo-master-0.svc-selector-demo-master"}\n]' > /etc/curvine.toml

IFS=: read -ra paths <<< "$FLUID_DATALOAD_DATA_PATH"
for p in "${paths[@]}"; do
/app/curvine/bin/cv load "$p" --watch --conf /etc/curvine.toml || {
echo "Error: load $p failed."
exit 1
}
done
topology:
master:
service: #需要为master创建Headless Service
headless: { }
dependencies:
extraResources:
# 使用 extraResources 时,需要定义其挂载路径
configMaps:
- name: curvine-config
mountPath: "/templates"
executionEntries:
mountUFS:
command:
- bash
- "-c"
- "/etc/curvine/mount/mountUfs.sh"
timeout: 30
template:
spec:
restartPolicy: Always
# 根据 runtime 生成的配置文件
initContainers:
- name: init-curvine
image: hairyhenderson/gomplate:alpine
# 挂载共享卷到容器内路径
volumeMounts:
- name: shared-config-volume
mountPath: /etc/curvine # 配置文件存放目录
command: [ "gomplate" ]
args: [ "-d", "config=$(FLUID_RUNTIME_CONFIG_PATH)", "-f", "/templates/cluster.toml", "-o", "/etc/curvine/curvine.toml" ]
containers:
- name: master
image: "curvine/curvine:latest"
command:
- /entrypoint.sh
args:
- master
- start
env:
# /entrypoint.sh 不支持参数指定配置文件,支撑环境变量配置,默认/app/curvine/conf/curvine-cluster.toml
- name: CURVINE_CONF_FILE
value: "/etc/curvine/curvine.toml"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
# curvine checks master hostname which should be one of journal_addrs
- name: CURVINE_MASTER_HOSTNAME
value: "$(POD_NAME).$(FLUID_RUNTIME_COMPONENT_SVC_NAME)"
volumeMounts:
- name: shared-config-volume
mountPath: /etc/curvine # 配置文件存放目录
- name: curvine-mount-volume
mountPath: /etc/curvine/mount # 配置文件存放目录
imagePullPolicy: IfNotPresent
volumes:
# emptyDir 共享存储(init容器和主容器互通)
- name: shared-config-volume
emptyDir: { }
- name: curvine-mount-volume
configMap:
name: curvine-mount
defaultMode: 0755
worker:
service:
headless: { } #需要为worker创建Headless Service
dependencies:
extraResources:
# 使用 extraResources 时,需要定义其挂载路径
configMaps:
- name: curvine-config
mountPath: "/templates"
template:
spec:
restartPolicy: Always
# 根据 runtime 生成的配置文件
initContainers:
- name: init-curvine
image: hairyhenderson/gomplate:alpine
# 挂载共享卷到容器内路径
volumeMounts:
- name: shared-config-volume
mountPath: /etc/curvine # 配置文件存放目录
command: [ "gomplate" ]
args: [ "-d", "config=$(FLUID_RUNTIME_CONFIG_PATH)", "-f", "/templates/cluster.toml", "-o", "/etc/curvine/curvine.toml" ]
containers:
- name: worker
image: "curvine/curvine:latest"
command:
- /entrypoint.sh
args:
- worker
- start
env:
# /entrypoint.sh 不支持参数指定配置文件,支撑环境变量配置,默认/app/curvine/conf/curvine-cluster.toml
- name: CURVINE_CONF_FILE
value: "/etc/curvine/curvine.toml"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: CURVINE_WORKER_HOSTNAME
value: "$(POD_NAME).$(FLUID_RUNTIME_COMPONENT_SVC_NAME)"
volumeMounts:
- name: shared-config-volume
mountPath: /etc/curvine # 配置文件存放目录
imagePullPolicy: IfNotPresent
volumes:
# emptyDir 共享存储(init容器和主容器互通)
- name: shared-config-volume
emptyDir: { }
client:
dependencies:
# Uncomment to enable secret mount for client (e.g., for JuiceFS FUSE pods)
# secretMount:
# enabled: true
extraResources:
# 使用 extraResources 时,需要定义其挂载路径
configMaps:
- name: curvine-config
mountPath: "/templates"
template:
spec:
restartPolicy: Always
# 根据 runtime 生成的配置文件
initContainers:
- name: init-curvine
image: hairyhenderson/gomplate:alpine
# 挂载共享卷到容器内路径
volumeMounts:
- name: shared-config-volume
mountPath: /etc/curvine # 配置文件存放目录
command: [ "gomplate" ]
args: [ "-d", "config=$(FLUID_RUNTIME_CONFIG_PATH)", "-f", "/templates/cluster.toml", "-o", "/etc/curvine/curvine.toml" ]
containers:
- name: client
image: "curvine/curvine:latest"
securityContext: #通常client需要配置privileged,用于操作fuse设备
privileged: true
runAsUser: 0
command:
- /app/curvine/lib/curvine-fuse
args:
- "--mnt-path"
- "$(FLUID_RUNTIME_MOUNT_PATH)"
- "--conf"
- "/etc/curvine/curvine.toml"
volumeMounts:
- name: shared-config-volume
mountPath: /etc/curvine # 配置文件存放目录
imagePullPolicy: IfNotPresent
volumes:
# emptyDir 共享存储(init容器和主容器互通)
- name: shared-config-volume
emptyDir: { }
Loading
Loading