Skip to content

Commit e981521

Browse files
author
玖宇
committed
feature
(fluid-webhook): support to update check-mount.sh configmap on demand Signed-off-by: 玖宇 <guotongyu.gty@alibaba-inc.com>
1 parent 9fe8f29 commit e981521

9 files changed

Lines changed: 590 additions & 13 deletions

File tree

pkg/application/inject/fuse/mount_point_script.go

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ import (
2323

2424
"github.com/fluid-cloudnative/fluid/pkg/application/inject/fuse/mutator"
2525
"github.com/fluid-cloudnative/fluid/pkg/application/inject/fuse/poststart"
26+
"github.com/fluid-cloudnative/fluid/pkg/common"
2627
"github.com/fluid-cloudnative/fluid/pkg/ddc/base"
2728
"github.com/fluid-cloudnative/fluid/pkg/utils"
2829
"github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient"
2930
corev1 "k8s.io/api/core/v1"
31+
"k8s.io/client-go/util/retry"
3032
)
3133

3234
func (s *Injector) injectCheckMountReadyScript(podSpecs *mutator.MutatingPodSpecs, runtimeInfos map[string]base.RuntimeInfoInterface) error {
@@ -131,24 +133,56 @@ func (s *Injector) ensureScriptConfigMapExists(namespace string) (*poststart.Scr
131133
appScriptGen := poststart.NewScriptGeneratorForApp(namespace)
132134

133135
cm := appScriptGen.BuildConfigmap()
134-
cmFound, err := kubeclient.IsConfigMapExist(s.client, cm.Name, cm.Namespace)
136+
cmKey := fmt.Sprintf("%s/%s", cm.Namespace, cm.Name)
137+
138+
existingCM, err := kubeclient.GetConfigmapByName(s.client, cm.Name, cm.Namespace)
135139
if err != nil {
136-
s.log.Error(err, "error when checking configMap's existence", "cm.Name", cm.Name, "cm.Namespace", cm.Namespace)
140+
s.log.Error(err, "error when getting configMap", "cm.Name", cm.Name, "cm.Namespace", cm.Namespace)
137141
return nil, err
138142
}
139143

140-
cmKey := fmt.Sprintf("%s/%s", cm.Namespace, cm.Name)
141-
s.log.V(1).Info("after check configMap existence", "configMap", cmKey, "existence", cmFound)
142-
if !cmFound {
144+
if existingCM == nil {
145+
// ConfigMap does not exist, create it
146+
s.log.V(1).Info("configMap not found, creating", "configMap", cmKey)
143147
err = s.client.Create(context.TODO(), cm)
144148
if err != nil {
145149
if otherErr := utils.IgnoreAlreadyExists(err); otherErr != nil {
146150
s.log.Error(err, "error when creating new configMap", "cm.Name", cm.Name, "cm.Namespace", cm.Namespace)
147151
return nil, err
148-
} else {
149-
s.log.V(1).Info("configmap already exists, skip", "configMap", cmKey)
150152
}
153+
s.log.V(1).Info("configmap already exists (concurrent creation), skip", "configMap", cmKey)
154+
}
155+
return appScriptGen, nil
156+
}
157+
158+
// ConfigMap exists, check if the script SHA256 annotation matches; update with retry on conflict.
159+
currentSHA256 := appScriptGen.GetScriptSHA256()
160+
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
161+
latest, getErr := kubeclient.GetConfigmapByName(s.client, cm.Name, cm.Namespace)
162+
if getErr != nil {
163+
return getErr
164+
}
165+
if latest == nil {
166+
// Deleted between Get calls; recreate
167+
return s.client.Create(context.TODO(), cm)
168+
}
169+
if latest.Annotations != nil {
170+
if annotationSHA256, ok := latest.Annotations[common.AnnotationCheckMountScriptSHA256]; ok && annotationSHA256 == currentSHA256 {
171+
s.log.V(1).Info("configmap script is up-to-date, skip update", "configMap", cmKey)
172+
return nil
173+
}
174+
}
175+
// SHA256 mismatch or annotation missing: update the ConfigMap with latest script and SHA256
176+
s.log.Info("configmap script SHA256 mismatch or annotation missing, updating", "configMap", cmKey, "expectedSHA256", currentSHA256)
177+
latest.Data = cm.Data
178+
if latest.Annotations == nil {
179+
latest.Annotations = map[string]string{}
151180
}
181+
latest.Annotations[common.AnnotationCheckMountScriptSHA256] = currentSHA256
182+
return s.client.Update(context.TODO(), latest)
183+
}); err != nil {
184+
s.log.Error(err, "error when ensuring configMap is up-to-date", "cm.Name", cm.Name, "cm.Namespace", cm.Namespace)
185+
return nil, err
152186
}
153187

154188
return appScriptGen, nil

pkg/application/inject/fuse/mount_point_script_test.go

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
"github.com/fluid-cloudnative/fluid/pkg/application/inject/fuse/mutator"
2323
"github.com/fluid-cloudnative/fluid/pkg/application/inject/fuse/poststart"
24+
"github.com/fluid-cloudnative/fluid/pkg/common"
2425
"github.com/fluid-cloudnative/fluid/pkg/ddc/base"
2526
"github.com/go-logr/logr"
2627
. "github.com/onsi/ginkgo/v2"
@@ -266,17 +267,64 @@ var _ = Describe("Fuse Injector", func() {
266267
})
267268
})
268269

269-
Context("when configmap already exists", func() {
270-
It("should not return an error", func() {
271-
// Create configmap first
270+
Context("when configmap already exists with matching SHA256 annotation", func() {
271+
It("should skip update and return without error", func() {
272272
appScriptGen := poststart.NewScriptGeneratorForApp(namespace)
273273
cm := appScriptGen.BuildConfigmap()
274274
err := fakeClient.Create(context.TODO(), cm)
275275
Expect(err).To(BeNil())
276276

277-
// Try to ensure it exists again
278277
_, err = injector.ensureScriptConfigMapExists(namespace)
279278
Expect(err).To(BeNil())
279+
280+
// Verify configmap is unchanged (no extra update)
281+
retrievedCM := &corev1.ConfigMap{}
282+
err = fakeClient.Get(context.TODO(), client.ObjectKey{Name: cm.Name, Namespace: cm.Namespace}, retrievedCM)
283+
Expect(err).To(BeNil())
284+
Expect(retrievedCM.Annotations).To(HaveKey(common.AnnotationCheckMountScriptSHA256))
285+
Expect(retrievedCM.Annotations[common.AnnotationCheckMountScriptSHA256]).To(Equal(appScriptGen.GetScriptSHA256()))
286+
})
287+
})
288+
289+
Context("when configmap already exists with stale SHA256 annotation", func() {
290+
It("should update the configmap with the latest script and annotation", func() {
291+
appScriptGen := poststart.NewScriptGeneratorForApp(namespace)
292+
cm := appScriptGen.BuildConfigmap()
293+
// Tamper the annotation to simulate an outdated configmap
294+
cm.Annotations[common.AnnotationCheckMountScriptSHA256] = "stale-sha256"
295+
cm.Data["check-fluid-mount-ready.sh"] = "old script content"
296+
err := fakeClient.Create(context.TODO(), cm)
297+
Expect(err).To(BeNil())
298+
299+
_, err = injector.ensureScriptConfigMapExists(namespace)
300+
Expect(err).To(BeNil())
301+
302+
// Verify configmap was updated
303+
retrievedCM := &corev1.ConfigMap{}
304+
err = fakeClient.Get(context.TODO(), client.ObjectKey{Name: cm.Name, Namespace: cm.Namespace}, retrievedCM)
305+
Expect(err).To(BeNil())
306+
Expect(retrievedCM.Annotations[common.AnnotationCheckMountScriptSHA256]).To(Equal(appScriptGen.GetScriptSHA256()))
307+
Expect(retrievedCM.Data["check-fluid-mount-ready.sh"]).NotTo(Equal("old script content"))
308+
})
309+
})
310+
311+
Context("when configmap already exists without SHA256 annotation", func() {
312+
It("should update the configmap to add the annotation", func() {
313+
appScriptGen := poststart.NewScriptGeneratorForApp(namespace)
314+
cm := appScriptGen.BuildConfigmap()
315+
// Remove the annotation to simulate a legacy configmap
316+
delete(cm.Annotations, common.AnnotationCheckMountScriptSHA256)
317+
err := fakeClient.Create(context.TODO(), cm)
318+
Expect(err).To(BeNil())
319+
320+
_, err = injector.ensureScriptConfigMapExists(namespace)
321+
Expect(err).To(BeNil())
322+
323+
retrievedCM := &corev1.ConfigMap{}
324+
err = fakeClient.Get(context.TODO(), client.ObjectKey{Name: cm.Name, Namespace: cm.Namespace}, retrievedCM)
325+
Expect(err).To(BeNil())
326+
Expect(retrievedCM.Annotations).To(HaveKey(common.AnnotationCheckMountScriptSHA256))
327+
Expect(retrievedCM.Annotations[common.AnnotationCheckMountScriptSHA256]).To(Equal(appScriptGen.GetScriptSHA256()))
280328
})
281329
})
282330
})

pkg/application/inject/fuse/mutator/mutator_default.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ import (
2222
"encoding/hex"
2323
"fmt"
2424
"path/filepath"
25+
"reflect"
2526
"strings"
2627
"time"
2728

2829
"github.com/go-logr/logr"
2930
"github.com/pkg/errors"
3031
corev1 "k8s.io/api/core/v1"
3132
"k8s.io/apimachinery/pkg/types"
33+
"k8s.io/client-go/util/retry"
3234
"sigs.k8s.io/controller-runtime/pkg/client"
3335

3436
"github.com/fluid-cloudnative/fluid/pkg/application/inject/fuse/poststart"
@@ -321,12 +323,14 @@ func prepareFuseContainerPostStartScript(helper *helperData) error {
321323
// Fluid assumes pvc name is the same with runtime's name
322324
gen := poststart.NewDefaultPostStartScriptGenerator()
323325
cmKey := gen.GetNamespacedConfigMapKey(types.NamespacedName{Namespace: datasetNamespace, Name: datasetName}, template.FuseMountInfo.FsType)
324-
found, err := kubeclient.IsConfigMapExist(helper.client, cmKey.Name, cmKey.Namespace)
326+
327+
existingCM, err := kubeclient.GetConfigmapByName(helper.client, cmKey.Name, cmKey.Namespace)
325328
if err != nil {
326329
return err
327330
}
328331

329-
if !found {
332+
if existingCM == nil {
333+
// ConfigMap does not exist, create it
330334
cm := gen.BuildConfigMap(dataset, cmKey)
331335
err = helper.client.Create(context.TODO(), cm)
332336
if err != nil {
@@ -335,6 +339,33 @@ func prepareFuseContainerPostStartScript(helper *helperData) error {
335339
return err
336340
}
337341
}
342+
} else {
343+
// ConfigMap exists; update with retry on conflict to handle concurrent webhook mutations.
344+
currentSHA256 := gen.GetScriptSHA256()
345+
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
346+
latest, getErr := kubeclient.GetConfigmapByName(helper.client, cmKey.Name, cmKey.Namespace)
347+
if getErr != nil {
348+
return getErr
349+
}
350+
if latest == nil {
351+
// Deleted between Get calls; recreate
352+
return helper.client.Create(context.TODO(), gen.BuildConfigMap(dataset, cmKey))
353+
}
354+
// Fast path: SHA256 annotation already matches, no update needed.
355+
if latest.Annotations != nil {
356+
if sha, ok := latest.Annotations[common.AnnotationCheckMountScriptSHA256]; ok && sha == currentSHA256 {
357+
return nil
358+
}
359+
}
360+
// Refresh Data/Labels/Annotations to match the desired state.
361+
updated := gen.RefreshConfigMapContents(dataset, cmKey, latest.DeepCopy())
362+
if reflect.DeepEqual(latest, updated) {
363+
return nil
364+
}
365+
return helper.client.Update(context.TODO(), updated)
366+
}); err != nil {
367+
return err
368+
}
338369
}
339370

340371
template.FuseContainer.VolumeMounts = append(template.FuseContainer.VolumeMounts, gen.GetVolumeMount())

pkg/application/inject/fuse/mutator/mutator_default_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package mutator
22

33
import (
4+
"context"
45
"fmt"
56

67
datav1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1"
@@ -619,3 +620,126 @@ var _ = Describe("Default mutator related unit tests", Label("pkg.application.in
619620
})
620621
})
621622
})
623+
624+
var _ = Describe("prepareFuseContainerPostStartScript ConfigMap update logic", Label("pkg.application.inject.fuse.mutator.mutator_default_test.go"), func() {
625+
var (
626+
testScheme *runtime.Scheme
627+
datasetName string
628+
datasetNamespace string
629+
dataset *datav1alpha1.Dataset
630+
thinRuntime *datav1alpha1.ThinRuntime
631+
daemonSet *appsv1.DaemonSet
632+
pv *corev1.PersistentVolume
633+
podToMutate *corev1.Pod
634+
)
635+
636+
BeforeEach(func() {
637+
testScheme = runtime.NewScheme()
638+
_ = datav1alpha1.AddToScheme(testScheme)
639+
_ = corev1.AddToScheme(testScheme)
640+
_ = appsv1.AddToScheme(testScheme)
641+
642+
datasetName = "test-dataset"
643+
datasetNamespace = "fluid"
644+
dataset, thinRuntime, daemonSet, pv = test_buildFluidResources(datasetName, datasetNamespace)
645+
podToMutate = test_buildPodToMutate([]string{datasetName})
646+
})
647+
648+
When("the configmap already exists with a matching SHA256 annotation", func() {
649+
It("should skip the update and not return an error", func() {
650+
c := fake.NewFakeClientWithScheme(testScheme, dataset, thinRuntime, daemonSet, pv)
651+
pod, err := applicationspod.NewApplication(podToMutate).GetPodSpecs()
652+
Expect(err).ShouldNot(HaveOccurred())
653+
specs, err := CollectFluidObjectSpecs(pod[0])
654+
Expect(err).NotTo(HaveOccurred())
655+
656+
args := MutatorBuildArgs{
657+
Client: c,
658+
Log: fake.NullLogger(),
659+
Options: common.FuseSidecarInjectOption{
660+
EnableCacheDir: false,
661+
SkipSidecarPostStartInject: false,
662+
},
663+
Specs: specs,
664+
}
665+
// First mutate: creates the ConfigMap
666+
mut := NewDefaultMutator(args)
667+
runtimeInfo, err := base.GetRuntimeInfo(c, datasetName, datasetNamespace)
668+
Expect(err).NotTo(HaveOccurred())
669+
err = mut.MutateWithRuntimeInfo(datasetName, runtimeInfo, "-0")
670+
Expect(err).To(BeNil())
671+
672+
// Re-build fresh args/mutator to simulate a second webhook call
673+
pod2, _ := applicationspod.NewApplication(podToMutate).GetPodSpecs()
674+
specs2, _ := CollectFluidObjectSpecs(pod2[0])
675+
args2 := MutatorBuildArgs{Client: c, Log: fake.NullLogger(), Options: args.Options, Specs: specs2}
676+
mut2 := NewDefaultMutator(args2)
677+
runtimeInfo2, err := base.GetRuntimeInfo(c, datasetName, datasetNamespace)
678+
Expect(err).NotTo(HaveOccurred())
679+
680+
// Second mutate: SHA256 matches, should not error
681+
err = mut2.MutateWithRuntimeInfo(datasetName, runtimeInfo2, "-0")
682+
Expect(err).To(BeNil())
683+
})
684+
})
685+
686+
When("the configmap already exists with a stale SHA256 annotation", func() {
687+
It("should refresh the configmap Data and annotation", func() {
688+
c := fake.NewFakeClientWithScheme(testScheme, dataset, thinRuntime, daemonSet, pv)
689+
pod, err := applicationspod.NewApplication(podToMutate).GetPodSpecs()
690+
Expect(err).ShouldNot(HaveOccurred())
691+
specs, err := CollectFluidObjectSpecs(pod[0])
692+
Expect(err).NotTo(HaveOccurred())
693+
694+
args := MutatorBuildArgs{
695+
Client: c,
696+
Log: fake.NullLogger(),
697+
Options: common.FuseSidecarInjectOption{
698+
EnableCacheDir: false,
699+
SkipSidecarPostStartInject: false,
700+
},
701+
Specs: specs,
702+
}
703+
// First mutate: creates the ConfigMap
704+
mut := NewDefaultMutator(args)
705+
runtimeInfo, err := base.GetRuntimeInfo(c, datasetName, datasetNamespace)
706+
Expect(err).NotTo(HaveOccurred())
707+
err = mut.MutateWithRuntimeInfo(datasetName, runtimeInfo, "-0")
708+
Expect(err).To(BeNil())
709+
710+
// Deliberately corrupt the SHA256 annotation to simulate a stale configmap
711+
cmList := &corev1.ConfigMapList{}
712+
Expect(c.List(context.TODO(), cmList)).To(Succeed())
713+
for i := range cmList.Items {
714+
cm := &cmList.Items[i]
715+
if cm.Annotations != nil {
716+
if _, ok := cm.Annotations[common.AnnotationCheckMountScriptSHA256]; ok {
717+
cm.Annotations[common.AnnotationCheckMountScriptSHA256] = "deliberately-stale-sha"
718+
Expect(c.Update(context.TODO(), cm)).To(Succeed())
719+
}
720+
}
721+
}
722+
723+
// Second mutate: SHA256 mismatch → should trigger update
724+
pod2, _ := applicationspod.NewApplication(podToMutate).GetPodSpecs()
725+
specs2, _ := CollectFluidObjectSpecs(pod2[0])
726+
args2 := MutatorBuildArgs{Client: c, Log: fake.NullLogger(), Options: args.Options, Specs: specs2}
727+
mut2 := NewDefaultMutator(args2)
728+
runtimeInfo2, err := base.GetRuntimeInfo(c, datasetName, datasetNamespace)
729+
Expect(err).NotTo(HaveOccurred())
730+
err = mut2.MutateWithRuntimeInfo(datasetName, runtimeInfo2, "-0")
731+
Expect(err).To(BeNil())
732+
733+
// Verify the SHA256 annotation was refreshed
734+
updatedCmList := &corev1.ConfigMapList{}
735+
Expect(c.List(context.TODO(), updatedCmList)).To(Succeed())
736+
for _, cm := range updatedCmList.Items {
737+
if cm.Annotations != nil {
738+
if sha, ok := cm.Annotations[common.AnnotationCheckMountScriptSHA256]; ok {
739+
Expect(sha).NotTo(Equal("deliberately-stale-sha"))
740+
}
741+
}
742+
}
743+
})
744+
})
745+
})

0 commit comments

Comments
 (0)