Skip to content

Commit

Permalink
Add volume readiness check and corresponding tests for 'CreateVolume'
Browse files Browse the repository at this point in the history
This commit introduces a new function, 'waitForVolumeAvailability', to the 'CreateVolume' function of our CSI driver. This function ensures that a volume reaches the 'Available' state before the 'CreateVolume' request is completed. To validate this functionality, corresponding tests have been added. This enhancement improves reliability and provides clearer error messages for debugging.
  • Loading branch information
sujeet01 committed Aug 25, 2023
1 parent f76284e commit aa9be38
Show file tree
Hide file tree
Showing 3 changed files with 261 additions and 124 deletions.
8 changes: 8 additions & 0 deletions pkg/driver/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package driver

import (
"time"

"github.com/onmetal/onmetal-csi-driver/pkg/utils"
"sigs.k8s.io/controller-runtime/pkg/client"
)
Expand Down Expand Up @@ -46,4 +48,10 @@ const (
CSIDriverName = "csi.onmetal.de"
topologyKey = "topology." + CSIDriverName + "/zone"
volumeFieldOwner = client.FieldOwner("csi.onmetal.de/volume")

// Constants for volume polling mechanism

waitVolumeInitDelay = 1 * time.Second // Initial delay before starting to poll for volume status
waitVolumeFactor = 1.1 // Factor by which the delay increases with each poll attempt
waitVolumeActiveSteps = 5 // Number of consecutive active steps to wait for volume status change
)
35 changes: 33 additions & 2 deletions pkg/driver/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/klog/v2"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -129,7 +130,12 @@ func (d *driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
if err := d.onmetalClient.Patch(ctx, volume, client.Apply, volumeFieldOwner, client.ForceOwnership); err != nil {
return nil, status.Errorf(codes.Internal, "Failed to patch volume %s: %v", client.ObjectKeyFromObject(volume), err)
}
klog.InfoS("Applied volume", "Volume", client.ObjectKeyFromObject(volume))

if err := waitForVolumeAvailability(ctx, d.onmetalClient, volume); err != nil {
return nil, fmt.Errorf("failed to confirm availability of the volume: %w", err)
}

klog.InfoS("Volume applied and is now available", "Volume", client.ObjectKeyFromObject(volume))

return &csi.CreateVolumeResponse{
Volume: &csi.Volume{
Expand All @@ -148,6 +154,31 @@ func (d *driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
}, nil
}

// waitForVolumeAvailability is a helper function that waits for a volume to become available.
// It uses an exponential backoff strategy to periodically check the status of the volume.
// The function returns an error if the volume does not become available within the specified number of attempts.
func waitForVolumeAvailability(ctx context.Context, onmetalClient client.Client, volume *storagev1alpha1.Volume) error {
backoff := wait.Backoff{
Duration: waitVolumeInitDelay,
Factor: waitVolumeFactor,
Steps: waitVolumeActiveSteps,
}

err := wait.ExponentialBackoffWithContext(ctx, backoff, func(ctx context.Context) (bool, error) {
err := onmetalClient.Get(ctx, client.ObjectKey{Namespace: volume.Namespace, Name: volume.Name}, volume)
if err == nil && volume.Status.State == storagev1alpha1.VolumeStateAvailable {
return true, nil
}
return false, err
})

if wait.Interrupted(err) {
return fmt.Errorf("volume %s did not reach 'Available' state within the defined timeout: %w", client.ObjectKeyFromObject(volume), err)
}

return err
}

func (d *driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {
klog.InfoS("Deleting volume", "Volume", req.GetVolumeId())
if req.GetVolumeId() == "" {
Expand All @@ -162,7 +193,7 @@ func (d *driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest)
if err := d.onmetalClient.Delete(ctx, vol); err != nil {
return nil, status.Errorf(codes.Internal, "Failed to delete volume %s: %v", client.ObjectKeyFromObject(vol), err)
}
klog.InfoS("Deleted deleted volume", "Volume", req.GetVolumeId())
klog.InfoS("Deleted volume", "Volume", req.GetVolumeId())
return &csi.DeleteVolumeResponse{}, nil
}

Expand Down
Loading

0 comments on commit aa9be38

Please sign in to comment.