From 960a59856601b6cf80cdb2f640d85e39dc92ea3c Mon Sep 17 00:00:00 2001 From: Jan Safranek Date: Thu, 25 Sep 2025 13:46:06 +0200 Subject: [PATCH] Enable nodeAllocatableUpdatePeriodSeconds in AWS EBS in TP AWS EBS CSI driver should support MutableCSINodeAllocatableCount feature. Set CSIDriver nodeAllocatableUpdatePeriodSeconds to 10 minutes when the feature gate is enabled. Kubelet will then call NodeGetDriverInfo every 10 minutes to update the attach limit of EBS volumes. --- assets/overlays/aws-ebs/base/csidriver.yaml | 6 +++ .../generated/hypershift/csidriver.yaml | 1 + .../generated/standalone/csidriver.yaml | 1 + pkg/driver/aws-ebs/aws_ebs.go | 28 ++++++++++++++ pkg/driver/aws-ebs/aws_ebs_test.go | 37 +++++++++++++++++++ 5 files changed, 73 insertions(+) diff --git a/assets/overlays/aws-ebs/base/csidriver.yaml b/assets/overlays/aws-ebs/base/csidriver.yaml index 1b16edddb..a99906ae8 100644 --- a/assets/overlays/aws-ebs/base/csidriver.yaml +++ b/assets/overlays/aws-ebs/base/csidriver.yaml @@ -14,3 +14,9 @@ spec: seLinuxMount: true volumeLifecycleModes: - Persistent + # We set the field even though the field is not available by default. + # The API server will clear the field if the field is not enabled. + # It would be complicated to add the field conditionally in the operator + # by our usual ${xxx} replacement, because yaml fields cannot start with '$'. + # And StaticResourceController does not allow any programmatic hooks. + nodeAllocatableUpdatePeriodSeconds: 600 diff --git a/assets/overlays/aws-ebs/generated/hypershift/csidriver.yaml b/assets/overlays/aws-ebs/generated/hypershift/csidriver.yaml index 4440f9e58..d176d7537 100644 --- a/assets/overlays/aws-ebs/generated/hypershift/csidriver.yaml +++ b/assets/overlays/aws-ebs/generated/hypershift/csidriver.yaml @@ -13,6 +13,7 @@ metadata: spec: attachRequired: true fsGroupPolicy: File + nodeAllocatableUpdatePeriodSeconds: 600 podInfoOnMount: false requiresRepublish: false seLinuxMount: true diff --git a/assets/overlays/aws-ebs/generated/standalone/csidriver.yaml b/assets/overlays/aws-ebs/generated/standalone/csidriver.yaml index 4440f9e58..d176d7537 100644 --- a/assets/overlays/aws-ebs/generated/standalone/csidriver.yaml +++ b/assets/overlays/aws-ebs/generated/standalone/csidriver.yaml @@ -13,6 +13,7 @@ metadata: spec: attachRequired: true fsGroupPolicy: File + nodeAllocatableUpdatePeriodSeconds: 600 podInfoOnMount: false requiresRepublish: false seLinuxMount: true diff --git a/pkg/driver/aws-ebs/aws_ebs.go b/pkg/driver/aws-ebs/aws_ebs.go index 2e2e6a6b7..1840bb47f 100644 --- a/pkg/driver/aws-ebs/aws_ebs.go +++ b/pkg/driver/aws-ebs/aws_ebs.go @@ -183,6 +183,9 @@ func GetAWSEBSOperatorControllerConfig(ctx context.Context, flavour generator.Cl if featureGates.Enabled(configv1.FeatureGateName("VolumeAttributesClass")) { cfg.AddDeploymentHookBuilders(c, withVolumeAttributesClassHook) } + if featureGates.Enabled(configv1.FeatureGateName("MutableCSINodeAllocatableCount")) { + cfg.AddDeploymentHookBuilders(c, withMutableCSINodeAllocatableCount) + } cfg.AddDaemonSetHookBuilders(c, withCABundleDaemonSetHook) cfg.AddStorageClassHookBuilders(c, withKMSKeyHook) @@ -208,6 +211,14 @@ func GetAWSEBSOperatorControllerConfig(ctx context.Context, flavour generator.Cl cfg.ExtraControlPlaneControllers = append(cfg.ExtraControlPlaneControllers, ctrl) } + cfg.ExtraReplacementsFunc = func() []string { + if featureGates.Enabled(configv1.FeatureGateName("MutableCSINodeAllocatableCount")) { + return nil + } + // CLEAR the field when the feature gate is disabled + return []string{"nodeAllocatableUpdatePeriodSeconds: 600", ""} + } + if flavour == generator.FlavourHyperShift { volumeTagController := NewEBSVolumeTagsController(cfg.GetControllerName("EBSVolumeTagsController"), c, c.EventRecorder) cfg.ExtraControlPlaneControllers = append(cfg.ExtraControlPlaneControllers, volumeTagController) @@ -500,3 +511,20 @@ func withVolumeAttributesClassHook(c *clients.Clients) (dc.DeploymentHookFunc, [ } return hook, nil } + +// withMutableCSINodeAllocatableCount enables the MutableCSINodeAllocatableCount feature gate in the external attacher +// TODO: remove when MutableCSINodeAllocatableCount is GA +func withMutableCSINodeAllocatableCount(c *clients.Clients) (dc.DeploymentHookFunc, []factory.Informer) { + hook := func(spec *opv1.OperatorSpec, deployment *appsv1.Deployment) error { + fgArgument := "--feature-gates=MutableCSINodeAllocatableCount=true" + for i := range deployment.Spec.Template.Spec.Containers { + container := &deployment.Spec.Template.Spec.Containers[i] + if container.Name != "csi-attacher" { + continue + } + container.Args = append(container.Args, fgArgument) + } + return nil + } + return hook, nil +} diff --git a/pkg/driver/aws-ebs/aws_ebs_test.go b/pkg/driver/aws-ebs/aws_ebs_test.go index a721f7861..746a51036 100644 --- a/pkg/driver/aws-ebs/aws_ebs_test.go +++ b/pkg/driver/aws-ebs/aws_ebs_test.go @@ -363,3 +363,40 @@ func Test_WithKMSKeyHook(t *testing.T) { }) } } + +func Test_WithMutableCSINodeAllocatableCount(t *testing.T) { + cr := clients.GetFakeOperatorCR() + c := clients.NewFakeClients("clusters-test", cr) + + hook, _ := withMutableCSINodeAllocatableCount(c) + deployment := getTestDeployment() + // Arrange - inject custom infrastructure + + // Act + err := hook(&cr.Spec.OperatorSpec, deployment) + if err != nil { + t.Fatalf("unexpected hook error: %v", err) + } + + // Assert + found := false + expectedFeatureGatesArg := "--feature-gates=MutableCSINodeAllocatableCount=true" + for _, container := range deployment.Spec.Template.Spec.Containers { + if container.Name == "csi-attacher" { + found = true + // Collect env vars from struct EnvVar to map[string]string + featureGatesArg := "" + for _, arg := range container.Args { + if strings.HasPrefix(arg, "--feature-gates") { + featureGatesArg = arg + } + } + if featureGatesArg != expectedFeatureGatesArg { + t.Errorf("expected csi-driver feature gates argument %s, got %s", expectedFeatureGatesArg, featureGatesArg) + } + } + } + if !found { + t.Errorf("container csi-attacher not found") + } +}