diff --git a/driver/controller.go b/driver/controller.go index 13244d8f..dcb1a09a 100644 --- a/driver/controller.go +++ b/driver/controller.go @@ -67,6 +67,9 @@ const ( // maxVolumesPerDropletErrorMessage is the error message returned by the DO // API when the per-droplet volume limit would be exceeded. maxVolumesPerDropletErrorMessage = "cannot attach more than 7 volumes to a single Droplet" + + // doneActionStatus is used to determine if a Digital Ocean resize action is completed. + doneActionStatus = "done" ) var ( @@ -162,6 +165,7 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) } contentSource := req.GetVolumeContentSource() + var snapshot *godo.Snapshot if contentSource != nil && contentSource.GetSnapshot() != nil { snapshotID := contentSource.GetSnapshot().GetSnapshotId() if snapshotID == "" { @@ -169,15 +173,20 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) } // check if the snapshot exist before we continue - _, resp, err := d.snapshots.Get(ctx, snapshotID) + var resp *godo.Response + snapshot, resp, err = d.snapshots.Get(ctx, snapshotID) if err != nil { if resp != nil && resp.StatusCode == http.StatusNotFound { return nil, status.Errorf(codes.NotFound, "snapshot %q does not exist", snapshotID) } return nil, err } + log = log.WithFields(logrus.Fields{ + "snapshot_id": snapshotID, + "snapshot_size_giga_bytes": snapshot.SizeGigaBytes, + }) + log.Info("using snapshot as volume source") - log.WithField("snapshot_id", snapshotID).Info("using snapshot as volume source") volumeReq.SnapshotID = snapshotID } @@ -191,6 +200,26 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) return nil, status.Error(codes.Internal, err.Error()) } + if snapshot != nil && volumeReq.SizeGigaBytes > int64(snapshot.SizeGigaBytes) && volumeReq.SizeGigaBytes > 1 { + log.Info("resizing volume because its requested size is larger than the size of the backing snapshot") + action, _, err := d.storageActions.Resize(ctx, vol.ID, int(volumeReq.SizeGigaBytes), volumeReq.Region) + if err != nil { + return nil, status.Errorf(codes.Internal, "cannot resize volume %s: %s", vol.ID, err.Error()) + } + log = log.WithFields(logrus.Fields{ + "resized_from": int(snapshot.SizeGigaBytes), + "resized_to": int(volumeReq.SizeGigaBytes), + }) + if action != nil && action.Status != doneActionStatus { + log = logWithAction(log, action) + log.Info("waiting until volume is resized") + if err := d.waitAction(ctx, log, vol.ID, action.ID); err != nil { + return nil, status.Errorf(codes.Internal, "failed waiting on action ID %d for volume ID %s to get resized: %s", action.ID, vol.ID, err) + } + } + log.Info("resize completed") + } + resp := &csi.CreateVolumeResponse{ Volume: &csi.Volume{ VolumeId: vol.ID, @@ -1023,7 +1052,7 @@ func (d *Driver) waitAction(ctx context.Context, log *logrus.Entry, volumeID str } log = log.WithField("action_status", action.Status) - if action.Status == godo.ActionCompleted { + if action.Status == godo.ActionCompleted || action.Status == doneActionStatus { log.Info("action completed") return true, nil } diff --git a/driver/node.go b/driver/node.go index dade6574..6e03a744 100644 --- a/driver/node.go +++ b/driver/node.go @@ -27,6 +27,8 @@ package driver import ( "context" "fmt" + "k8s.io/klog/v2" + "os" "path/filepath" "strings" @@ -35,6 +37,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "k8s.io/mount-utils" + mountutil "k8s.io/mount-utils" utilexec "k8s.io/utils/exec" ) @@ -166,6 +169,22 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe log.Info("source device is already mounted to the target path") } + if _, err := os.Stat(source); err == nil { + r := mountutil.NewResizeFs(utilexec.New()) + needResize, err := r.NeedResize(source, target) + + if err != nil { + return nil, status.Errorf(codes.Internal, "Could not determine if volume %q need to be resized: %v", req.VolumeId, err) + } + + if needResize { + klog.V(4).Infof("NodeStageVolume: Resizing volume %q created from a snapshot/volume", req.VolumeId) + if _, err := r.Resize(source, target); err != nil { + return nil, status.Errorf(codes.Internal, "Could not resize volume %q: %v", req.VolumeId, err) + } + } + } + log.Info("formatting and mounting stage volume is finished") return &csi.NodeStageVolumeResponse{}, nil } diff --git a/go.mod b/go.mod index 4995271a..6fd51fda 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( google.golang.org/grpc v1.51.0 gotest.tools/v3 v3.4.0 k8s.io/apimachinery v0.27.1 + k8s.io/klog/v2 v2.90.1 k8s.io/mount-utils v0.27.1 k8s.io/utils v0.0.0-20230209194617-a36077c30491 ) @@ -53,7 +54,6 @@ require ( gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.90.1 // indirect ) replace k8s.io/api => k8s.io/api v0.27.1 diff --git a/test/e2e/testdrivers/1.27.yaml b/test/e2e/testdrivers/1.27.yaml index 51ff5310..f121f8e6 100644 --- a/test/e2e/testdrivers/1.27.yaml +++ b/test/e2e/testdrivers/1.27.yaml @@ -27,7 +27,7 @@ DriverInfo: fsGroup: true volumeMountGroup: false exec: true - snapshotDataSource: false + snapshotDataSource: true pvcDataSource: false multipods: true RWX: false