Skip to content
7 changes: 3 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ require (
github.com/container-storage-interface/spec v1.11.0
github.com/google/go-cmp v0.7.0
github.com/kubernetes-csi/csi-lib-utils v0.22.0
golang.org/x/oauth2 v0.27.0 // indirect
golang.org/x/term v0.31.0 // indirect
google.golang.org/grpc v1.72.1
k8s.io/api v0.34.0
k8s.io/apimachinery v0.34.0
Expand All @@ -16,10 +14,9 @@ require (
k8s.io/component-base v0.34.0
k8s.io/csi-translation-lib v0.34.0
k8s.io/klog/v2 v2.130.1
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
)

require k8s.io/utils v0.0.0-20250604170112-4c0f3b243397

require (
cel.dev/expr v0.24.0 // indirect
github.com/NYTimes/gziphandler v1.1.1 // indirect
Expand Down Expand Up @@ -87,8 +84,10 @@ require (
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/oauth2 v0.27.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/term v0.31.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/time v0.9.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
Expand Down
4 changes: 2 additions & 2 deletions pkg/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,15 +293,15 @@ func (ctrl *resizeController) Run(workers int, ctx context.Context, wg *sync.Wai
}

if utilfeature.DefaultFeatureGate.Enabled(features.ReleaseLeaderElectionOnExit) {
for i := 0; i < workers; i++ {
for range workers {
wg.Add(1)
go func() {
defer wg.Done()
wait.Until(ctrl.syncPVCs, 0, stopCh)
}()
}
} else {
for i := 0; i < workers; i++ {
for range workers {
go wait.Until(ctrl.syncPVCs, 0, stopCh)
}
}
Expand Down
20 changes: 10 additions & 10 deletions pkg/controller/resize_status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,42 +30,42 @@ func TestResizeFunctions(t *testing.T) {
}{
{
name: "mark fs resize, with no other conditions",
pvc: basePVC.Get(),
expectedPVC: basePVC.WithStorageResourceStatus(v1.PersistentVolumeClaimNodeResizePending).Get(),
pvc: basePVC().Get(),
expectedPVC: basePVC().WithStorageResourceStatus(v1.PersistentVolumeClaimNodeResizePending).Get(),
testFunc: func(pvc *v1.PersistentVolumeClaim, ctrl *resizeController, size resource.Quantity) (*v1.PersistentVolumeClaim, error) {
return ctrl.markForPendingNodeExpansion(pvc)
},
},
{
name: "mark fs resize, when other resource statuses are present",
pvc: basePVC.WithResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeInfeasible).Get(),
expectedPVC: basePVC.WithResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeInfeasible).
pvc: basePVC().WithResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeInfeasible).Get(),
expectedPVC: basePVC().WithResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeInfeasible).
WithStorageResourceStatus(v1.PersistentVolumeClaimNodeResizePending).Get(),
testFunc: func(pvc *v1.PersistentVolumeClaim, ctrl *resizeController, _ resource.Quantity) (*v1.PersistentVolumeClaim, error) {
return ctrl.markForPendingNodeExpansion(pvc)
},
},
{
name: "mark controller resize in-progress",
pvc: basePVC.Get(),
expectedPVC: basePVC.WithStorageResourceStatus(v1.PersistentVolumeClaimControllerResizeInProgress).Get(),
pvc: basePVC().Get(),
expectedPVC: basePVC().WithStorageResourceStatus(v1.PersistentVolumeClaimControllerResizeInProgress).Get(),
testFunc: func(pvc *v1.PersistentVolumeClaim, ctrl *resizeController, q resource.Quantity) (*v1.PersistentVolumeClaim, error) {
return ctrl.markControllerResizeInProgress(pvc, q, true)
},
},
{
name: "mark controller resize failed",
pvc: basePVC.Get(),
expectedPVC: basePVC.WithStorageResourceStatus(v1.PersistentVolumeClaimControllerResizeInfeasible).Get(),
pvc: basePVC().Get(),
expectedPVC: basePVC().WithStorageResourceStatus(v1.PersistentVolumeClaimControllerResizeInfeasible).Get(),
testFunc: func(pvc *v1.PersistentVolumeClaim, ctrl *resizeController, q resource.Quantity) (*v1.PersistentVolumeClaim, error) {
return ctrl.markControllerExpansionInfeasible(pvc, fmt.Errorf("things failed"))
},
},
{
name: "mark resize finished",
pvc: basePVC.WithResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeInfeasible).
pvc: basePVC().WithResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeInfeasible).
WithStorageResourceStatus(v1.PersistentVolumeClaimNodeResizePending).Get(),
expectedPVC: basePVC.WithResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeInfeasible).
expectedPVC: basePVC().WithResourceStatus(v1.ResourceCPU, v1.PersistentVolumeClaimControllerResizeInfeasible).
WithStorageResourceStatus("").Get(),
testFunc: func(pvc *v1.PersistentVolumeClaim, ctrl *resizeController, q resource.Quantity) (*v1.PersistentVolumeClaim, error) {
return ctrl.markOverallExpansionAsFinished(pvc, q)
Expand Down
45 changes: 24 additions & 21 deletions pkg/modifycontroller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/kubernetes-csi/csi-lib-utils/slowset"
"github.com/kubernetes-csi/external-resizer/pkg/modifier"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
Expand All @@ -41,6 +42,7 @@ import (
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue"
"k8s.io/klog/v2"
"k8s.io/utils/ptr"
)

// ModifyController watches PVCs and checks if they are requesting an modify operation.
Expand All @@ -63,8 +65,11 @@ type modifyController struct {
vacLister storagev1listers.VolumeAttributesClassLister
vacListerSynced cache.InformerSynced
extraModifyMetadata bool
// the key of the map is {PVC_NAMESPACE}/{PVC_NAME}
uncertainPVCs map[string]v1.PersistentVolumeClaim
// uncertainPVCs tracks PVCs that failed with non-final errors.
// We must not change the target when retrying.
// All in-progress PVCs are added here on initialization.
// The key of the map is {PVC_NAMESPACE}/{PVC_NAME}, value is not important now.
uncertainPVCs sync.Map
// slowSet tracks PVCs for which modification failed with infeasible error and should be retried at slower rate.
slowSet *slowset.SlowSet
}
Expand Down Expand Up @@ -124,19 +129,18 @@ func NewModifyController(
}

func (ctrl *modifyController) initUncertainPVCs() error {
ctrl.uncertainPVCs = make(map[string]v1.PersistentVolumeClaim)
allPVCs, err := ctrl.pvcLister.List(labels.Everything())
if err != nil {
klog.Errorf("Failed to list pvcs when init uncertain pvcs: %v", err)
return err
}
for _, pvc := range allPVCs {
if pvc.Status.ModifyVolumeStatus != nil && (pvc.Status.ModifyVolumeStatus.Status == v1.PersistentVolumeClaimModifyVolumeInProgress || pvc.Status.ModifyVolumeStatus.Status == v1.PersistentVolumeClaimModifyVolumeInfeasible) {
if pvc.Status.ModifyVolumeStatus != nil && (pvc.Status.ModifyVolumeStatus.Status == v1.PersistentVolumeClaimModifyVolumeInProgress) {
pvcKey, err := cache.MetaNamespaceKeyFunc(pvc)
if err != nil {
return err
}
ctrl.uncertainPVCs[pvcKey] = *pvc.DeepCopy()
ctrl.uncertainPVCs.Store(pvcKey, pvc)
}
}

Expand All @@ -163,18 +167,18 @@ func (ctrl *modifyController) updatePVC(oldObj, newObj interface{}) {
}

// Only trigger modify volume if the following conditions are met
// 1. Non empty vac name
// 2. oldVacName != newVacName
// 3. PVC is in Bound state
oldVacName := oldPVC.Spec.VolumeAttributesClassName
newVacName := newPVC.Spec.VolumeAttributesClassName
if newVacName != nil && *newVacName != "" && (oldVacName == nil || *newVacName != *oldVacName) && oldPVC.Status.Phase == v1.ClaimBound {
// 1. VAC changed or modify finished (check pending modify request while we are modifying)
// 2. PVC is in Bound state
oldVacName := ptr.Deref(oldPVC.Spec.VolumeAttributesClassName, "")
newVacName := ptr.Deref(newPVC.Spec.VolumeAttributesClassName, "")
if (newVacName != oldVacName || newPVC.Status.ModifyVolumeStatus == nil) && newPVC.Status.Phase == v1.ClaimBound {
_, err := ctrl.pvLister.Get(oldPVC.Spec.VolumeName)
if err != nil {
klog.Errorf("Get PV %q of pvc %q in PVInformer cache failed: %v", oldPVC.Spec.VolumeName, klog.KObj(oldPVC), err)
return
}
// Handle modify volume by adding to the claimQueue to avoid race conditions
klog.V(4).InfoS("Enqueueing PVC for modify", "PVC", klog.KObj(newPVC))
ctrl.addPVC(newObj)
} else {
klog.V(4).InfoS("No need to modify PVC", "PVC", klog.KObj(newPVC))
Expand All @@ -190,10 +194,7 @@ func (ctrl *modifyController) deletePVC(obj interface{}) {
}

func (ctrl *modifyController) init(ctx context.Context) bool {
informersSyncd := []cache.InformerSynced{ctrl.pvListerSynced, ctrl.pvcListerSynced}
informersSyncd = append(informersSyncd, ctrl.vacListerSynced)

if !cache.WaitForCacheSync(ctx.Done(), informersSyncd...) {
if !cache.WaitForCacheSync(ctx.Done(), ctrl.pvListerSynced, ctrl.pvcListerSynced, ctrl.vacListerSynced) {
klog.ErrorS(nil, "Cannot sync pod, pv, pvc or vac caches")
return false
}
Expand Down Expand Up @@ -224,15 +225,15 @@ func (ctrl *modifyController) Run(
go ctrl.slowSet.Run(stopCh)

if utilfeature.DefaultFeatureGate.Enabled(features.ReleaseLeaderElectionOnExit) {
for i := 0; i < workers; i++ {
for range workers {
wg.Add(1)
go func() {
defer wg.Done()
wait.Until(ctrl.sync, 0, stopCh)
}()
}
} else {
for i := 0; i < workers; i++ {
for range workers {
go wait.Until(ctrl.sync, 0, stopCh)
}
}
Expand Down Expand Up @@ -268,6 +269,10 @@ func (ctrl *modifyController) syncPVC(key string) error {

pvc, err := ctrl.pvcLister.PersistentVolumeClaims(namespace).Get(name)
if err != nil {
if apierrors.IsNotFound(err) {
klog.V(3).InfoS("PVC is deleted or does not exist", "PVC", klog.KRef(namespace, name))
return nil
}
return fmt.Errorf("getting PVC %s/%s failed: %v", namespace, name, err)
}

Expand All @@ -283,15 +288,13 @@ func (ctrl *modifyController) syncPVC(key string) error {

// Only trigger modify volume if the following conditions are met
// 1. PV provisioned by CSI driver AND driver name matches local driver
// 2. Non-empty vac name
// 3. PVC is in Bound state
// 2. PVC is in Bound state
if pv.Spec.CSI == nil || pv.Spec.CSI.Driver != ctrl.name {
klog.V(7).InfoS("Skipping PV provisioned by different driver", "PV", klog.KObj(pv))
return nil
}

vacName := pvc.Spec.VolumeAttributesClassName
if vacName != nil && *vacName != "" && pvc.Status.Phase == v1.ClaimBound {
if pvc.Status.Phase == v1.ClaimBound {
_, _, err, _ := ctrl.modify(pvc, pv)
if err != nil {
return err
Expand Down
Loading