diff --git a/.github/workflows/pr-check.yaml b/.github/workflows/pr-check.yaml index 580fa9f..a446d66 100644 --- a/.github/workflows/pr-check.yaml +++ b/.github/workflows/pr-check.yaml @@ -6,28 +6,28 @@ on: jobs: lint: name: Lint - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - - name: Setup up Go 1.x + - name: Setup up Go 1.23 uses: actions/setup-go@v5 with: - go-version: "^1.15" + go-version: "1.23" - name: Check out code uses: actions/checkout@v4 - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: - version: v1.58.2 + version: v1.63.4 args: --timeout=5m build: name: Test & Build - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - - name: Setup up Go 1.x + - name: Setup up Go 1.23 uses: actions/setup-go@v5 with: - go-version: "^1.15" + go-version: "1.23" - name: Check out code uses: actions/checkout@v4 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index e877eb9..d2aac42 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -14,7 +14,7 @@ env: jobs: push: name: Push images - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - name: Check out code @@ -67,7 +67,7 @@ jobs: release: name: Release - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 # Run only if previous job has succeeded needs: [push] diff --git a/go.mod b/go.mod index 84aa4cf..3eb5dfb 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/shapeblue/cloudstack-csi-driver -go 1.21 +go 1.23 + +toolchain go1.23.5 require ( github.com/apache/cloudstack-go/v2 v2.16.1 @@ -12,6 +14,7 @@ require ( golang.org/x/sys v0.20.0 golang.org/x/text v0.16.0 google.golang.org/grpc v1.65.0 + google.golang.org/protobuf v1.34.1 gopkg.in/gcfg.v1 v1.2.3 k8s.io/api v0.29.7 k8s.io/apimachinery v0.29.7 @@ -67,7 +70,6 @@ require ( golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect - google.golang.org/protobuf v1.34.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/pkg/cloud/cloud.go b/pkg/cloud/cloud.go index 0c21e3d..b683f88 100644 --- a/pkg/cloud/cloud.go +++ b/pkg/cloud/cloud.go @@ -10,6 +10,8 @@ import ( ) // Interface is the CloudStack client interface. + +//nolint:interfacebloat type Interface interface { GetNodeInfo(ctx context.Context, vmName string) (*VM, error) GetVMByID(ctx context.Context, vmID string) (*VM, error) @@ -24,10 +26,12 @@ type Interface interface { DetachVolume(ctx context.Context, volumeID string) error ExpandVolume(ctx context.Context, volumeID string, newSizeInGB int64) error - CreateVolumeFromSnapshot(ctx context.Context, zoneID, name, domainID, projectID, snapshotID string, sizeInGB int64) (*Volume, error) + CreateVolumeFromSnapshot(ctx context.Context, zoneID, name, projectID, snapshotID string, sizeInGB int64) (*Volume, error) GetSnapshotByID(ctx context.Context, snapshotID string) (*Snapshot, error) - CreateSnapshot(ctx context.Context, volumeID string) (*Snapshot, error) + GetSnapshotByName(ctx context.Context, name string) (*Snapshot, error) + CreateSnapshot(ctx context.Context, volumeID, name string) (*Snapshot, error) DeleteSnapshot(ctx context.Context, snapshotID string) error + ListSnapshots(ctx context.Context, volumeID, snapshotID string) ([]*Snapshot, error) } // Volume represents a CloudStack volume. @@ -70,6 +74,7 @@ type VM struct { var ( ErrNotFound = errors.New("not found") ErrTooManyResults = errors.New("too many results") + ErrAlreadyExists = errors.New("already exists") ) // client is the implementation of Interface. diff --git a/pkg/cloud/config.go b/pkg/cloud/config.go index 0024dff..70d15a3 100644 --- a/pkg/cloud/config.go +++ b/pkg/cloud/config.go @@ -3,7 +3,7 @@ package cloud import ( "fmt" - "gopkg.in/gcfg.v1" + gcfg "gopkg.in/gcfg.v1" ) // Config holds CloudStack connection configuration. diff --git a/pkg/cloud/fake/fake.go b/pkg/cloud/fake/fake.go index 2834867..781c12a 100644 --- a/pkg/cloud/fake/fake.go +++ b/pkg/cloud/fake/fake.go @@ -4,6 +4,7 @@ package fake import ( "context" + "errors" "github.com/hashicorp/go-uuid" @@ -12,13 +13,13 @@ import ( ) const zoneID = "a1887604-237c-4212-a9cd-94620b7880fa" -const snapshotID = "9d076136-657b-4c84-b279-455da3ea484c" type fakeConnector struct { - node *cloud.VM - snapshot *cloud.Snapshot - volumesByID map[string]cloud.Volume - volumesByName map[string]cloud.Volume + node *cloud.VM + volumesByID map[string]cloud.Volume + volumesByName map[string]cloud.Volume + snapshotsByID map[string]*cloud.Snapshot + snapshotsByName map[string][]*cloud.Snapshot } // New returns a new fake implementation of the @@ -38,20 +39,15 @@ func New() cloud.Interface { ZoneID: zoneID, } - snapshot := &cloud.Snapshot{ - ID: "9d076136-657b-4c84-b279-455da3ea484c", - Name: "pvc-vol-snap-1", - DomainID: "51f0fcb5-db16-4637-94f5-30131010214f", - ZoneID: zoneID, - VolumeID: "4f1f610d-6f17-4ff9-9228-e4062af93e54", - CreatedAt: "2025-07-07 16:13:06", - } + snapshotsByID := make(map[string]*cloud.Snapshot) + snapshotsByName := make(map[string][]*cloud.Snapshot) return &fakeConnector{ - node: node, - snapshot: snapshot, - volumesByID: map[string]cloud.Volume{volume.ID: volume}, - volumesByName: map[string]cloud.Volume{volume.Name: volume}, + node: node, + volumesByID: map[string]cloud.Volume{volume.ID: volume}, + volumesByName: map[string]cloud.Volume{volume.Name: volume}, + snapshotsByID: snapshotsByID, + snapshotsByName: snapshotsByName, } } @@ -72,6 +68,9 @@ func (f *fakeConnector) ListZonesID(_ context.Context) ([]string, error) { } func (f *fakeConnector) GetVolumeByID(_ context.Context, volumeID string) (*cloud.Volume, error) { + if volumeID == "" { + return nil, errors.New("invalid volume ID: empty string") + } vol, ok := f.volumesByID[volumeID] if ok { return &vol, nil @@ -81,6 +80,9 @@ func (f *fakeConnector) GetVolumeByID(_ context.Context, volumeID string) (*clou } func (f *fakeConnector) GetVolumeByName(_ context.Context, name string) (*cloud.Volume, error) { + if name == "" { + return nil, errors.New("invalid volume name: empty string") + } vol, ok := f.volumesByName[name] if ok { return &vol, nil @@ -137,18 +139,120 @@ func (f *fakeConnector) ExpandVolume(_ context.Context, volumeID string, newSize return cloud.ErrNotFound } -func (f *fakeConnector) CreateVolumeFromSnapshot(ctx context.Context, zoneID, name, domainID, projectID, snapshotID string, sizeInGB int64) (*cloud.Volume, error) { - return nil, nil +func (f *fakeConnector) CreateVolumeFromSnapshot(_ context.Context, zoneID, name, _, _ string, sizeInGB int64) (*cloud.Volume, error) { + vol := &cloud.Volume{ + ID: "fake-vol-from-snap-" + name, + Name: name, + Size: util.GigaBytesToBytes(sizeInGB), + DiskOfferingID: "fake-disk-offering", + ZoneID: zoneID, + } + f.volumesByID[vol.ID] = *vol + f.volumesByName[vol.Name] = *vol + + return vol, nil } -func (f *fakeConnector) GetSnapshotByID(ctx context.Context, snapshotID string) (*cloud.Snapshot, error) { - return f.snapshot, nil +func (f *fakeConnector) CreateSnapshot(_ context.Context, volumeID, name string) (*cloud.Snapshot, error) { + if name == "" { + return nil, errors.New("invalid snapshot name: empty string") + } + for _, snap := range f.snapshotsByName[name] { + if snap.VolumeID == volumeID { + // Allow multiple snapshots with the same name for the same volume + continue + } + + // Name conflict: same name, different volume + return nil, cloud.ErrAlreadyExists + } + id, _ := uuid.GenerateUUID() + newSnap := &cloud.Snapshot{ + ID: id, + Name: name, + DomainID: "fake-domain", + ZoneID: zoneID, + VolumeID: volumeID, + CreatedAt: "2025-07-07T16:13:06-0700", + } + f.snapshotsByID[newSnap.ID] = newSnap + f.snapshotsByName[name] = append(f.snapshotsByName[name], newSnap) + + return newSnap, nil } -func (f *fakeConnector) CreateSnapshot(ctx context.Context, volumeID string) (*cloud.Snapshot, error) { - return f.snapshot, nil +func (f *fakeConnector) GetSnapshotByID(_ context.Context, snapshotID string) (*cloud.Snapshot, error) { + snap, ok := f.snapshotsByID[snapshotID] + if ok { + return snap, nil + } + + return nil, cloud.ErrNotFound } -func (f *fakeConnector) DeleteSnapshot(ctx context.Context, snapshotID string) error { +func (f *fakeConnector) GetSnapshotByName(_ context.Context, name string) (*cloud.Snapshot, error) { + if name == "" { + return nil, errors.New("invalid snapshot name: empty string") + } + snaps, ok := f.snapshotsByName[name] + if ok && len(snaps) > 0 { + return snaps[0], nil // Return the first for compatibility + } + + return nil, cloud.ErrNotFound +} + +// ListSnapshots returns all matching snapshots; pagination must be handled by the controller. +func (f *fakeConnector) ListSnapshots(_ context.Context, volumeID, snapshotID string) ([]*cloud.Snapshot, error) { + if snapshotID != "" { + result := make([]*cloud.Snapshot, 0, 1) + if snap, ok := f.snapshotsByID[snapshotID]; ok { + result = append(result, snap) + } + + return result, nil + } + if volumeID != "" { + count := 0 + for _, snap := range f.snapshotsByID { + if snap.VolumeID == volumeID { + count++ + } + } + result := make([]*cloud.Snapshot, 0, count) + for _, snap := range f.snapshotsByID { + if snap.VolumeID == volumeID { + result = append(result, snap) + } + } + + return result, nil + } + result := make([]*cloud.Snapshot, 0, len(f.snapshotsByID)) + for _, snap := range f.snapshotsByID { + result = append(result, snap) + } + + return result, nil +} + +func (f *fakeConnector) DeleteSnapshot(_ context.Context, snapshotID string) error { + snap, ok := f.snapshotsByID[snapshotID] + if !ok { + return cloud.ErrNotFound + } + + delete(f.snapshotsByID, snapshotID) + + name := snap.Name + snaps := f.snapshotsByName[name] + for i, s := range snaps { + if s.ID == snapshotID { + f.snapshotsByName[name] = append(snaps[:i], snaps[i+1:]...) + + break + } + } + return nil } diff --git a/pkg/cloud/snapshots.go b/pkg/cloud/snapshots.go index 74830e1..d501021 100644 --- a/pkg/cloud/snapshots.go +++ b/pkg/cloud/snapshots.go @@ -45,12 +45,15 @@ func (c *client) GetSnapshotByID(ctx context.Context, snapshotID string) (*Snaps return &s, nil } -func (c *client) CreateSnapshot(ctx context.Context, volumeID string) (*Snapshot, error) { +func (c *client) CreateSnapshot(ctx context.Context, volumeID, name string) (*Snapshot, error) { logger := klog.FromContext(ctx) p := c.Snapshot.NewCreateSnapshotParams(volumeID) - + if name != "" { + p.SetName(name) + } logger.V(2).Info("CloudStack API call", "command", "CreateSnapshot", "params", map[string]string{ "volumeid": volumeID, + "name": name, }) snapshot, err := c.Snapshot.CreateSnapshot(p) @@ -68,10 +71,11 @@ func (c *client) CreateSnapshot(ctx context.Context, volumeID string) (*Snapshot VolumeID: snapshot.Volumeid, CreatedAt: snapshot.Created, } + return &snap, nil } -func (c *client) DeleteSnapshot(ctx context.Context, snapshotID string) error { +func (c *client) DeleteSnapshot(_ context.Context, snapshotID string) error { p := c.Snapshot.NewDeleteSnapshotParams(snapshotID) _, err := c.Snapshot.DeleteSnapshot(p) if err != nil && strings.Contains(err.Error(), "4350") { @@ -81,3 +85,83 @@ func (c *client) DeleteSnapshot(ctx context.Context, snapshotID string) error { return err } + +func (c *client) GetSnapshotByName(ctx context.Context, name string) (*Snapshot, error) { + logger := klog.FromContext(ctx) + if name == "" { + return nil, ErrNotFound + } + p := c.Snapshot.NewListSnapshotsParams() + p.SetName(name) + if c.projectID != "" { + p.SetProjectid(c.projectID) + } + logger.V(2).Info("CloudStack API call", "command", "ListSnapshots", "params", map[string]string{ + "name": name, + "projectid": c.projectID, + }) + l, err := c.Snapshot.ListSnapshots(p) + if err != nil { + return nil, err + } + if l.Count == 0 { + return nil, ErrNotFound + } + if l.Count > 1 { + return nil, ErrTooManyResults + } + snapshot := l.Snapshots[0] + s := Snapshot{ + ID: snapshot.Id, + Name: snapshot.Name, + DomainID: snapshot.Domainid, + ProjectID: snapshot.Projectid, + ZoneID: snapshot.Zoneid, + VolumeID: snapshot.Volumeid, + CreatedAt: snapshot.Created, + } + + return &s, nil +} + +func (c *client) ListSnapshots(ctx context.Context, volumeID, snapshotID string) ([]*Snapshot, error) { + logger := klog.FromContext(ctx) + p := c.Snapshot.NewListSnapshotsParams() + if snapshotID != "" { + p.SetId(snapshotID) + } + if volumeID != "" { + p.SetVolumeid(volumeID) + } + if c.projectID != "" { + p.SetProjectid(c.projectID) + } + logger.V(2).Info("CloudStack API call", "command", "ListSnapshots", "params", map[string]string{ + "id": snapshotID, + "volumeid": volumeID, + "projectid": c.projectID, + }) + l, err := c.Snapshot.ListSnapshots(p) + if err != nil { + return nil, err + } + if l.Count == 0 { + return []*Snapshot{}, nil + } + result := make([]*Snapshot, 0, l.Count) + for _, snapshot := range l.Snapshots { + s := &Snapshot{ + ID: snapshot.Id, + Name: snapshot.Name, + Size: snapshot.Virtualsize, + DomainID: snapshot.Domainid, + ProjectID: snapshot.Projectid, + ZoneID: snapshot.Zoneid, + VolumeID: snapshot.Volumeid, + CreatedAt: snapshot.Created, + } + result = append(result, s) + } + + return result, nil +} diff --git a/pkg/cloud/vms.go b/pkg/cloud/vms.go index 2e98f64..354f3a2 100644 --- a/pkg/cloud/vms.go +++ b/pkg/cloud/vms.go @@ -29,6 +29,7 @@ func (c *client) GetVMByID(ctx context.Context, vmID string) (*VM, error) { } vm := l.VirtualMachines[0] logger.V(2).Info("Returning VM", "vmID", vm.Id, "zoneID", vm.Zoneid) + return &VM{ ID: vm.Id, ZoneID: vm.Zoneid, diff --git a/pkg/cloud/volumes.go b/pkg/cloud/volumes.go index 3d1b363..154e28b 100644 --- a/pkg/cloud/volumes.go +++ b/pkg/cloud/volumes.go @@ -164,7 +164,7 @@ func (c *client) ExpandVolume(ctx context.Context, volumeID string, newSizeInGB return nil } -func (c *client) CreateVolumeFromSnapshot(ctx context.Context, zoneID, name, domainID, projectID, snapshotID string, sizeInGB int64) (*Volume, error) { +func (c *client) CreateVolumeFromSnapshot(ctx context.Context, zoneID, name, projectID, snapshotID string, sizeInGB int64) (*Volume, error) { logger := klog.FromContext(ctx) p := c.Volume.NewCreateVolumeParams() diff --git a/pkg/driver/controller.go b/pkg/driver/controller.go index 293ba16..f487010 100644 --- a/pkg/driver/controller.go +++ b/pkg/driver/controller.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "math/rand" + "strconv" "time" "github.com/container-storage-interface/spec/lib/go/csi" @@ -49,6 +50,7 @@ func NewControllerServer(connector cloud.Interface) csi.ControllerServer { } } +//nolint:gocognit func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { logger := klog.FromContext(ctx) logger.V(6).Info("CreateVolume: called", "args", *req) @@ -148,7 +150,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol sizeInGB = snapshotSizeGiB } - volFromSnapshot, err := cs.connector.CreateVolumeFromSnapshot(ctx, snapshot.ZoneID, name, snapshot.DomainID, snapshot.ProjectID, snapshotID, sizeInGB) + volFromSnapshot, err := cs.connector.CreateVolumeFromSnapshot(ctx, snapshot.ZoneID, name, snapshot.ProjectID, snapshotID, sizeInGB) if err != nil { return nil, status.Errorf(codes.Internal, "Cannot create volume from snapshot %s: %v", snapshotID, err.Error()) } @@ -164,6 +166,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol }, }, } + return resp, nil } @@ -224,6 +227,7 @@ func printVolumeAsJSON(vol *csi.CreateVolumeRequest) { b, err := json.MarshalIndent(vol, "", " ") if err != nil { klog.Errorf("Failed to marshal CreateVolumeRequest to JSON: %v", err) + return } klog.V(5).Infof("CreateVolumeRequest as JSON:\n%s", string(b)) @@ -328,17 +332,32 @@ func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVol func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) { klog.V(4).Infof("CreateSnapshot") + if req.GetName() == "" { + return nil, status.Error(codes.InvalidArgument, "Snapshot name missing in request") + } + volumeID := req.GetSourceVolumeId() + if volumeID == "" { + return nil, status.Error(codes.InvalidArgument, "SourceVolumeId missing in request") + } + volume, err := cs.connector.GetVolumeByID(ctx, volumeID) - if errors.Is(err, cloud.ErrNotFound) { - return nil, status.Errorf(codes.NotFound, "Volume %v not found", volumeID) - } else if err != nil { - // Error with CloudStack + if err != nil { + if err.Error() == "invalid volume ID: empty string" { + return nil, status.Error(codes.InvalidArgument, "Invalid volume ID") + } + if errors.Is(err, cloud.ErrNotFound) { + return nil, status.Errorf(codes.NotFound, "Volume %v not found", volumeID) + } + return nil, status.Errorf(codes.Internal, "Error %v", err) } - klog.V(4).Infof("CreateSnapshot of volume: %s", volume) - snapshot, err := cs.connector.CreateSnapshot(ctx, volume.ID) - if err != nil { + + klog.V(4).Infof("CreateSnapshot of volume: %s", volume.ID) + snapshot, err := cs.connector.CreateSnapshot(ctx, volume.ID, req.GetName()) + if errors.Is(err, cloud.ErrAlreadyExists) { + return nil, status.Errorf(codes.AlreadyExists, "Snapshot name conflict: already exists for a different source volume") + } else if err != nil { return nil, status.Errorf(codes.Internal, "Failed to create snapshot for volume %s: %v", volume.ID, err.Error()) } @@ -347,7 +366,6 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS return nil, status.Errorf(codes.Internal, "Failed to parse snapshot creation time: %v", err) } - // Convert to Timestamp protobuf ts := timestamppb.New(t) resp := &csi.CreateSnapshotResponse{ @@ -356,11 +374,55 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS SourceVolumeId: volume.ID, CreationTime: ts, ReadyToUse: true, - // We leave the optional SizeBytes field unset as the size of a block storage snapshot is the size of the difference to the volume or previous snapshots, k8s however expects the size to be the size of the restored volume. }, } + return resp, nil +} + +func (cs *controllerServer) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) { + entries := []*csi.ListSnapshotsResponse_Entry{} + + snapshots, err := cs.connector.ListSnapshots(ctx, req.GetSourceVolumeId(), req.GetSnapshotId()) + if err != nil { + return nil, status.Errorf(codes.Internal, "Failed to list snapshots: %v", err) + } + // Pagination logic + start := 0 + if req.GetStartingToken() != "" { + var err error + start, err = strconv.Atoi(req.GetStartingToken()) + if err != nil || start < 0 || start > len(snapshots) { + return nil, status.Error(codes.Aborted, "Invalid startingToken") + } + } + maxEntries := int(req.GetMaxEntries()) + end := len(snapshots) + if maxEntries > 0 && start+maxEntries < end { + end = start + maxEntries + } + nextToken := "" + if end < len(snapshots) { + nextToken = strconv.Itoa(end) + } + + for i := start; i < end; i++ { + snap := snapshots[i] + t, _ := time.Parse("2006-01-02T15:04:05-0700", snap.CreatedAt) + ts := timestamppb.New(t) + entry := &csi.ListSnapshotsResponse_Entry{ + Snapshot: &csi.Snapshot{ + SnapshotId: snap.ID, + SourceVolumeId: snap.VolumeID, + CreationTime: ts, + ReadyToUse: true, + }, + } + entries = append(entries, entry) + } + + return &csi.ListSnapshotsResponse{Entries: entries, NextToken: nextToken}, nil } func (cs *controllerServer) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) { @@ -372,19 +434,14 @@ func (cs *controllerServer) DeleteSnapshot(ctx context.Context, req *csi.DeleteS klog.V(4).Infof("DeleteSnapshot for snapshotID: %s", snapshotID) - snapshot, err := cs.connector.GetSnapshotByID(ctx, snapshotID) + err := cs.connector.DeleteSnapshot(ctx, snapshotID) if errors.Is(err, cloud.ErrNotFound) { - return nil, status.Errorf(codes.NotFound, "Snapshot %v not found", snapshotID) + // Per CSI spec, return OK if snapshot does not exist + return &csi.DeleteSnapshotResponse{}, nil } else if err != nil { - // Error with CloudStack return nil, status.Errorf(codes.Internal, "Error %v", err) } - err = cs.connector.DeleteSnapshot(ctx, snapshot.ID) - if err != nil && !errors.Is(err, cloud.ErrNotFound) { - return nil, status.Errorf(codes.Internal, "Cannot delete snapshot %s: %s", snapshotID, err.Error()) - } - return &csi.DeleteSnapshotResponse{}, nil } @@ -680,14 +737,14 @@ func (cs *controllerServer) ControllerGetCapabilities(ctx context.Context, req * }, }, }, - &csi.ControllerServiceCapability{ + { Type: &csi.ControllerServiceCapability_Rpc{ Rpc: &csi.ControllerServiceCapability_RPC{ Type: csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT, }, }, }, - &csi.ControllerServiceCapability{ + { Type: &csi.ControllerServiceCapability_Rpc{ Rpc: &csi.ControllerServiceCapability_RPC{ Type: csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS, diff --git a/pkg/mount/mount.go b/pkg/mount/mount.go index 578c539..827e73c 100644 --- a/pkg/mount/mount.go +++ b/pkg/mount/mount.go @@ -95,6 +95,7 @@ func (m *mounter) GetDevicePath(ctx context.Context, volumeID string) (string, e if path != "" { devicePath = path logger.V(4).Info("Device path found", "volumeID", volumeID, "devicePath", path) + return true, nil } m.probeVolume(ctx) @@ -142,6 +143,7 @@ func (m *mounter) getDevicePathBySerialID(ctx context.Context, volumeID string) } if !os.IsNotExist(err) { logger.Error(err, "Failed to stat device path", "path", source) + return "", err } } @@ -161,11 +163,13 @@ func (m *mounter) getDevicePathForXenServer(ctx context.Context, volumeID string if err == nil && isBlock { if m.verifyDevice(ctx, devicePath, volumeID) { logger.V(4).Info("Found and verified XenServer device", "devicePath", devicePath, "volumeID", volumeID) + return devicePath, nil } } } } + return "", fmt.Errorf("device not found for volume %s", volumeID) } @@ -182,11 +186,13 @@ func (m *mounter) getDevicePathForVMware(ctx context.Context, volumeID string) ( if err == nil && isBlock { if m.verifyDevice(ctx, devicePath, volumeID) { logger.V(4).Info("Found and verified VMware device", "devicePath", devicePath, "volumeID", volumeID) + return devicePath, nil } } } } + return "", fmt.Errorf("device not found for volume %s", volumeID) } @@ -196,6 +202,7 @@ func (m *mounter) verifyDevice(ctx context.Context, devicePath string, volumeID size, err := m.GetBlockSizeBytes(devicePath) if err != nil { logger.V(4).Info("Failed to get device size", "devicePath", devicePath, "volumeID", volumeID, "error", err) + return false } logger.V(5).Info("Device size retrieved", "devicePath", devicePath, "volumeID", volumeID, "sizeBytes", size) @@ -203,16 +210,19 @@ func (m *mounter) verifyDevice(ctx context.Context, devicePath string, volumeID mounted, err := m.isDeviceMounted(devicePath) if err != nil { logger.V(4).Info("Failed to check if device is mounted", "devicePath", devicePath, "volumeID", volumeID, "error", err) + return false } if mounted { logger.V(4).Info("Device is already mounted", "devicePath", devicePath, "volumeID", volumeID) + return false } props, err := m.getDeviceProperties(devicePath) if err != nil { logger.V(4).Info("Failed to get device properties", "devicePath", devicePath, "volumeID", volumeID, "error", err) + return false } logger.V(5).Info("Device properties retrieved", "devicePath", devicePath, "volumeID", volumeID, "properties", props) @@ -226,19 +236,10 @@ func (m *mounter) isDeviceMounted(devicePath string) (bool, error) { if strings.Contains(err.Error(), "exit status 1") { return false, nil } - return false, err - } - return len(output) > 0, nil -} -func (m *mounter) isDeviceInUse(devicePath string) (bool, error) { - output, err := m.Exec.Command("lsof", devicePath).Output() - if err != nil { - if strings.Contains(err.Error(), "exit status 1") { - return false, nil - } return false, err } + return len(output) > 0, nil } @@ -377,13 +378,13 @@ func (m *mounter) GetStatistics(volumePath string) (volumeStatistics, error) { } volStats := volumeStatistics{ - AvailableBytes: int64(statfs.Bavail) * int64(statfs.Bsize), //nolint:unconvert - TotalBytes: int64(statfs.Blocks) * int64(statfs.Bsize), //nolint:unconvert - UsedBytes: (int64(statfs.Blocks) - int64(statfs.Bfree)) * int64(statfs.Bsize), //nolint:unconvert + AvailableBytes: int64(statfs.Bavail) * int64(statfs.Bsize), //nolint:gosec,unconvert + TotalBytes: int64(statfs.Blocks) * int64(statfs.Bsize), //nolint:gosec,unconvert + UsedBytes: (int64(statfs.Blocks) - int64(statfs.Bfree)) * int64(statfs.Bsize), //nolint:gosec,unconvert - AvailableInodes: int64(statfs.Ffree), - TotalInodes: int64(statfs.Files), - UsedInodes: int64(statfs.Files) - int64(statfs.Ffree), + AvailableInodes: int64(statfs.Ffree), //nolint:gosec + TotalInodes: int64(statfs.Files), //nolint:gosec + UsedInodes: int64(statfs.Files) - int64(statfs.Ffree), //nolint:gosec } return volStats, nil