diff --git a/pkg/ddc/cache/component/advanced_statefulset_manager.go b/pkg/ddc/cache/component/advanced_statefulset_manager.go index bb9af248ca0..3dc43376b90 100644 --- a/pkg/ddc/cache/component/advanced_statefulset_manager.go +++ b/pkg/ddc/cache/component/advanced_statefulset_manager.go @@ -216,8 +216,10 @@ func (s *AdvancedStatefulSetManager) SyncComponentSpec(ctx context.Context, iden } // 3. Update resources if specified - if s.updateResources(astsToUpdate, newSpec.Resources, logger) { - needsUpdate = true + if newSpec.Resources != nil { + if s.updateResources(astsToUpdate, *newSpec.Resources, logger) { + needsUpdate = true + } } // Skip patching if no changes detected diff --git a/pkg/ddc/cache/component/component_manager.go b/pkg/ddc/cache/component/component_manager.go index ce90dcaa51b..7f18149a8fe 100644 --- a/pkg/ddc/cache/component/component_manager.go +++ b/pkg/ddc/cache/component/component_manager.go @@ -44,7 +44,7 @@ type ComponentSpec struct { // Version contains image and pull policy information Version datav1alpha1.VersionSpec // Resources contains CPU and memory resource requirements - Resources corev1.ResourceRequirements + Resources *corev1.ResourceRequirements } func NewComponentHelper(componentType common.ComponentType, client client.Client) ComponentManager { diff --git a/pkg/ddc/cache/component/sync_component_spec_test.go b/pkg/ddc/cache/component/sync_component_spec_test.go index 1af1648e43f..d719169e36b 100644 --- a/pkg/ddc/cache/component/sync_component_spec_test.go +++ b/pkg/ddc/cache/component/sync_component_spec_test.go @@ -233,7 +233,7 @@ var _ = Describe("AdvancedStatefulSetManager SyncComponentSpec", func() { Context("when updating resources", func() { It("should update both requests and limits", func() { spec := ComponentSpec{ - Resources: corev1.ResourceRequirements{ + Resources: &corev1.ResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceCPU: resource.MustParse("4"), corev1.ResourceMemory: resource.MustParse("8Gi"), @@ -265,7 +265,7 @@ var _ = Describe("AdvancedStatefulSetManager SyncComponentSpec", func() { It("should not update when resources unchanged", func() { spec := ComponentSpec{ - Resources: corev1.ResourceRequirements{ + Resources: &corev1.ResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceCPU: resource.MustParse("2"), corev1.ResourceMemory: resource.MustParse("4Gi"), @@ -302,7 +302,7 @@ var _ = Describe("AdvancedStatefulSetManager SyncComponentSpec", func() { Image: "fluid-cache", ImageTag: "v1.1.0", }, - Resources: corev1.ResourceRequirements{ + Resources: &corev1.ResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceCPU: resource.MustParse("4"), }, diff --git a/pkg/ddc/cache/engine/sync.go b/pkg/ddc/cache/engine/sync.go index c7c45c6c24e..ea3ef178914 100644 --- a/pkg/ddc/cache/engine/sync.go +++ b/pkg/ddc/cache/engine/sync.go @@ -195,9 +195,9 @@ func (e *CacheEngine) syncRuntimeSpec(ctx cruntime.ReconcileRequestContext, runt manager := component.NewComponentHelper(common.ComponentTypeMaster, e.Client) // Only sync resources if they are explicitly set (not zero-value) // This prevents overwriting template defaults when user hasn't specified resources - var resources corev1.ResourceRequirements + var resources *corev1.ResourceRequirements if runtime.Spec.Master.Resources.Requests != nil || runtime.Spec.Master.Resources.Limits != nil { - resources = runtime.Spec.Master.Resources + resources = &runtime.Spec.Master.Resources } masterSpec := component.ComponentSpec{ Version: runtime.Spec.Master.RuntimeVersion, @@ -219,9 +219,9 @@ func (e *CacheEngine) syncRuntimeSpec(ctx cruntime.ReconcileRequestContext, runt manager := component.NewComponentHelper(common.ComponentTypeWorker, e.Client) // Only sync resources if they are explicitly set (not zero-value) // This prevents overwriting template defaults when user hasn't specified resources - var workerResources corev1.ResourceRequirements + var workerResources *corev1.ResourceRequirements if runtime.Spec.Worker.Resources.Requests != nil || runtime.Spec.Worker.Resources.Limits != nil { - workerResources = runtime.Spec.Worker.Resources + workerResources = &runtime.Spec.Worker.Resources } workerSpec := component.ComponentSpec{ Version: runtime.Spec.Worker.RuntimeVersion, diff --git a/pkg/ddc/cache/engine/sync_test.go b/pkg/ddc/cache/engine/sync_test.go index 509a417dbb6..6f84fd38157 100644 --- a/pkg/ddc/cache/engine/sync_test.go +++ b/pkg/ddc/cache/engine/sync_test.go @@ -33,6 +33,7 @@ import ( cruntime "github.com/fluid-cloudnative/fluid/pkg/runtime" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -858,4 +859,77 @@ var _ = Describe("CacheEngine Sync Tests", Label("pkg.ddc.cache.engine.sync_test }) }) }) + + Describe("syncRuntimeSpec", func() { + const masterSts, workerSts = "test-runtime-master", "test-runtime-worker" + + // templateResources mirrors the value the creation path derives from the + // CacheRuntimeClass template, i.e. what a sync must leave untouched. + templateResources := corev1.ResourceRequirements{ + Limits: corev1.ResourceList{corev1.ResourceMemory: resource.MustParse("2Gi")}, + } + + // seedTemplateResources reproduces the post-creation state: the template declares + // resources and the already-created workload carries them. + seedTemplateResources := func(stsName string, template *corev1.PodTemplateSpec) { + template.Spec.Containers[0].Resources = *templateResources.DeepCopy() + + sts := &workloadv1alpha1.AdvancedStatefulSet{} + key := types.NamespacedName{Name: stsName, Namespace: "default"} + Expect(fakeClient.Get(ctx.Context, key, sts)).To(Succeed()) + sts.Spec.Template.Spec.Containers[0].Resources = *templateResources.DeepCopy() + Expect(fakeClient.Update(ctx.Context, sts)).To(Succeed()) + } + + memLimitOf := func(stsName string) string { + sts := &workloadv1alpha1.AdvancedStatefulSet{} + key := types.NamespacedName{Name: stsName, Namespace: "default"} + Expect(fakeClient.Get(ctx.Context, key, sts)).To(Succeed()) + limit := sts.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceMemory] + return limit.String() + } + + BeforeEach(func() { + seedTemplateResources(masterSts, &runtimeClass.Topology.Master.Template) + seedTemplateResources(workerSts, &runtimeClass.Topology.Worker.Template) + }) + + Context("when the CacheRuntime does not specify resources", func() { + It("should leave the template's resources untouched", func() { + Expect(runtimeObj.Spec.Master.Resources.Limits).To(BeNil()) + Expect(runtimeObj.Spec.Master.Resources.Requests).To(BeNil()) + Expect(runtimeObj.Spec.Worker.Resources.Limits).To(BeNil()) + Expect(runtimeObj.Spec.Worker.Resources.Requests).To(BeNil()) + + Expect(engine.syncRuntimeSpec(ctx, runtimeObj, runtimeClass)).To(Succeed()) + + Expect(memLimitOf(masterSts)).To(Equal("2Gi")) + Expect(memLimitOf(workerSts)).To(Equal("2Gi")) + }) + }) + + Context("when the CacheRuntime specifies resources", func() { + It("should apply them to the master workload only", func() { + runtimeObj.Spec.Master.Resources = corev1.ResourceRequirements{ + Limits: corev1.ResourceList{corev1.ResourceMemory: resource.MustParse("4Gi")}, + } + + Expect(engine.syncRuntimeSpec(ctx, runtimeObj, runtimeClass)).To(Succeed()) + + Expect(memLimitOf(masterSts)).To(Equal("4Gi")) + Expect(memLimitOf(workerSts)).To(Equal("2Gi")) + }) + + It("should apply them to the worker workload only", func() { + runtimeObj.Spec.Worker.Resources = corev1.ResourceRequirements{ + Limits: corev1.ResourceList{corev1.ResourceMemory: resource.MustParse("4Gi")}, + } + + Expect(engine.syncRuntimeSpec(ctx, runtimeObj, runtimeClass)).To(Succeed()) + + Expect(memLimitOf(workerSts)).To(Equal("4Gi")) + Expect(memLimitOf(masterSts)).To(Equal("2Gi")) + }) + }) + }) })