Skip to content

Commit a7c9690

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 a7c9690

9 files changed

Lines changed: 632 additions & 24 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+
latestSHA256 := appScriptGen.GetScriptSHA256()
160+
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
161+
current, getErr := kubeclient.GetConfigmapByName(s.client, cm.Name, cm.Namespace)
162+
if getErr != nil {
163+
return getErr
164+
}
165+
if current == nil {
166+
// Deleted between Get calls; recreate
167+
return s.client.Create(context.TODO(), cm)
168+
}
169+
if current.Annotations != nil {
170+
if annotationSHA256, ok := current.Annotations[common.AnnotationCheckMountScriptSHA256]; ok && annotationSHA256 == latestSHA256 {
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", latestSHA256)
177+
current.Data = cm.Data
178+
if current.Annotations == nil {
179+
current.Annotations = map[string]string{}
151180
}
181+
current.Annotations[common.AnnotationCheckMountScriptSHA256] = latestSHA256
182+
return s.client.Update(context.TODO(), current)
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: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,24 @@ 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"
3537
"github.com/fluid-cloudnative/fluid/pkg/common"
3638
"github.com/fluid-cloudnative/fluid/pkg/ddc/base"
3739
"github.com/fluid-cloudnative/fluid/pkg/utils"
3840
"github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient"
41+
42+
datav1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1"
3943
)
4044

4145
var (
@@ -321,20 +325,9 @@ func prepareFuseContainerPostStartScript(helper *helperData) error {
321325
// Fluid assumes pvc name is the same with runtime's name
322326
gen := poststart.NewDefaultPostStartScriptGenerator()
323327
cmKey := gen.GetNamespacedConfigMapKey(types.NamespacedName{Namespace: datasetNamespace, Name: datasetName}, template.FuseMountInfo.FsType)
324-
found, err := kubeclient.IsConfigMapExist(helper.client, cmKey.Name, cmKey.Namespace)
325-
if err != nil {
326-
return err
327-
}
328328

329-
if !found {
330-
cm := gen.BuildConfigMap(dataset, cmKey)
331-
err = helper.client.Create(context.TODO(), cm)
332-
if err != nil {
333-
// If ConfigMap creation succeeds concurrently, continue to mutate
334-
if otherErr := utils.IgnoreAlreadyExists(err); otherErr != nil {
335-
return err
336-
}
337-
}
329+
if err = ensurePostStartConfigMap(helper.client, gen, dataset, cmKey); err != nil {
330+
return err
338331
}
339332

340333
template.FuseContainer.VolumeMounts = append(template.FuseContainer.VolumeMounts, gen.GetVolumeMount())
@@ -347,6 +340,63 @@ func prepareFuseContainerPostStartScript(helper *helperData) error {
347340
return nil
348341
}
349342

343+
// ensurePostStartConfigMap creates the ConfigMap if it does not exist, or updates it when the
344+
// script content has changed (detected via SHA256 annotation).
345+
func ensurePostStartConfigMap(c client.Client, gen poststart.ScriptGenerator, dataset *datav1alpha1.Dataset, cmKey types.NamespacedName) error {
346+
existingCM, err := kubeclient.GetConfigmapByName(c, cmKey.Name, cmKey.Namespace)
347+
if err != nil {
348+
return err
349+
}
350+
351+
if existingCM == nil {
352+
cm := gen.BuildConfigMap(dataset, cmKey)
353+
if createErr := c.Create(context.TODO(), cm); createErr != nil {
354+
// If ConfigMap creation succeeds concurrently, continue to mutate
355+
return utils.IgnoreAlreadyExists(createErr)
356+
}
357+
return nil
358+
}
359+
360+
// ConfigMap exists; update with retry on conflict to handle concurrent webhook mutations.
361+
return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
362+
return updateConfigMapIfStale(c, gen, dataset, cmKey)
363+
})
364+
}
365+
366+
// updateConfigMapIfStale fetches the current ConfigMap from the cluster and updates it when the
367+
// SHA256 annotation does not match the latest script. It is designed to be called inside a
368+
// RetryOnConflict loop.
369+
func updateConfigMapIfStale(c client.Client, gen poststart.ScriptGenerator, dataset *datav1alpha1.Dataset, cmKey types.NamespacedName) error {
370+
current, err := kubeclient.GetConfigmapByName(c, cmKey.Name, cmKey.Namespace)
371+
if err != nil {
372+
return err
373+
}
374+
if current == nil {
375+
// Deleted between Get calls; recreate
376+
return c.Create(context.TODO(), gen.BuildConfigMap(dataset, cmKey))
377+
}
378+
379+
if isConfigMapUpToDate(current, gen.GetScriptSHA256()) {
380+
return nil
381+
}
382+
383+
latest := gen.RefreshConfigMapContents(dataset, cmKey, current.DeepCopy())
384+
if reflect.DeepEqual(current, latest) {
385+
return nil
386+
}
387+
return c.Update(context.TODO(), latest)
388+
}
389+
390+
// isConfigMapUpToDate returns true when the annotations already carry the expected SHA256,
391+
// meaning no update is needed.
392+
func isConfigMapUpToDate(cm *corev1.ConfigMap, expectedSHA256 string) bool {
393+
if cm == nil || cm.Annotations == nil {
394+
return false
395+
}
396+
sha, ok := cm.Annotations[common.AnnotationCheckMountScriptSHA256]
397+
return ok && sha == expectedSHA256
398+
}
399+
350400
func transformTemplateWithCacheDirDisabled(helper *helperData) {
351401
template := helper.template
352402
template.FuseContainer.VolumeMounts = utils.TrimVolumeMounts(template.FuseContainer.VolumeMounts, cacheDirNames)

0 commit comments

Comments
 (0)