@@ -20,6 +20,7 @@ import (
2020 "context"
2121 "errors"
2222 "fmt"
23+ "sort"
2324 "strconv"
2425 "time"
2526
@@ -769,76 +770,127 @@ func (cs *controllerServer) ListSnapshots(ctx context.Context, req *csi.ListSnap
769770 if snapshotID != "" {
770771 snap , err := cloud .GetSnapshot (ctx , snapshotID )
771772 if err != nil {
772- if stackiterrors .IsNotFound (err ) {
773- klog .V (3 ).Infof ("Snapshot %s not found" , snapshotID )
774- return & csi.ListSnapshotsResponse {}, nil
773+ if ! stackiterrors .IsNotFound (err ) {
774+ return nil , status .Errorf (codes .Internal , "Failed to GetSnapshot %s: %v" , snapshotID , err )
775775 }
776- return nil , status .Errorf (codes .Internal , "Failed to GetSnapshot %s: %v" , snapshotID , err )
777- }
778776
779- ctime := timestamppb .New (* snap .CreatedAt )
777+ backup , backupErr := cloud .GetBackup (ctx , snapshotID )
778+ if backupErr != nil {
779+ if stackiterrors .IsNotFound (backupErr ) {
780+ klog .V (3 ).Infof ("Snapshot or backup %s not found" , snapshotID )
781+ return & csi.ListSnapshotsResponse {}, nil
782+ }
783+ return nil , status .Errorf (codes .Internal , "Failed to GetBackup %s: %v" , snapshotID , backupErr )
784+ }
780785
781- entry := & csi.ListSnapshotsResponse_Entry {
782- Snapshot : & csi.Snapshot {
783- SizeBytes : * snap .Size * util .GIBIBYTE ,
784- SnapshotId : * snap .Id ,
785- SourceVolumeId : snap .VolumeId ,
786- CreationTime : ctime ,
787- ReadyToUse : true ,
788- },
786+ entry := backupSnapshotEntry (* backup )
787+ return & csi.ListSnapshotsResponse {
788+ Entries : []* csi.ListSnapshotsResponse_Entry {entry },
789+ }, entry .Snapshot .CreationTime .CheckValid ()
789790 }
790791
791- entries := []* csi.ListSnapshotsResponse_Entry {entry }
792792 return & csi.ListSnapshotsResponse {
793- Entries : entries ,
794- }, ctime .CheckValid ()
793+ Entries : [] * csi. ListSnapshotsResponse_Entry { snapshotEntry ( * snap )} ,
794+ }, timestamppb . New ( * snap . CreatedAt ) .CheckValid ()
795795 }
796796
797- filters := map [string ]string {}
798-
799- var slist []iaas.Snapshot
800- var err error
801- var nextPageToken string
797+ startOffset := 0
798+ if req .GetStartingToken () != "" {
799+ var err error
800+ startOffset , err = strconv .Atoi (req .GetStartingToken ())
801+ if err != nil || startOffset < 0 {
802+ return nil , status .Error (codes .Aborted , "starting_token is invalid" )
803+ }
804+ }
802805
803- // Add the filters
806+ filters := map [string ]string {
807+ "Status" : stackitclient .SnapshotReadyStatus ,
808+ }
804809 if req .GetSourceVolumeId () != "" {
805810 filters ["VolumeID" ] = req .GetSourceVolumeId ()
806- } else {
807- filters ["Limit" ] = strconv .Itoa (int (req .MaxEntries ))
808- filters ["Marker" ] = req .StartingToken
809811 }
810812
811- // Only retrieve snapshots that are available
812- filters ["Status" ] = stackitclient .SnapshotReadyStatus
813- slist , nextPageToken , err = cloud .ListSnapshots (ctx , filters )
813+ snapshotList , _ , err := cloud .ListSnapshots (ctx , filters )
814814 if err != nil {
815815 klog .Errorf ("Failed to ListSnapshots: %v" , err )
816816 return nil , status .Errorf (codes .Internal , "ListSnapshots failed with error %v" , err )
817817 }
818818
819- sentries := make ([]* csi.ListSnapshotsResponse_Entry , 0 , len (slist ))
820- for _ , v := range slist {
821- ctime := timestamppb .New (* v .CreatedAt )
822- if err := ctime .CheckValid (); err != nil {
823- klog .Errorf ("Error to convert time to timestamp: %v" , err )
824- }
825- sentry := csi.ListSnapshotsResponse_Entry {
826- Snapshot : & csi.Snapshot {
827- SizeBytes : * v .Size * util .GIBIBYTE ,
828- SnapshotId : * v .Id ,
829- SourceVolumeId : v .VolumeId ,
830- CreationTime : ctime ,
831- ReadyToUse : true ,
832- },
819+ backupList , err := cloud .ListBackups (ctx , filters )
820+ if err != nil {
821+ klog .Errorf ("Failed to ListBackups: %v" , err )
822+ return nil , status .Errorf (codes .Internal , "ListBackups failed with error %v" , err )
823+ }
824+
825+ entries := make ([]* csi.ListSnapshotsResponse_Entry , 0 , len (snapshotList )+ len (backupList ))
826+ for _ , v := range snapshotList {
827+ entries = append (entries , snapshotEntry (v ))
828+ }
829+ for _ , v := range backupList {
830+ entries = append (entries , backupSnapshotEntry (v ))
831+ }
832+
833+ // sort by creation time and by id (fallback)
834+ sort .Slice (entries , func (i , j int ) bool {
835+ left := entries [i ].Snapshot
836+ right := entries [j ].Snapshot
837+ if left .CreationTime .AsTime ().Equal (right .CreationTime .AsTime ()) {
838+ return left .SnapshotId < right .SnapshotId
833839 }
834- sentries = append (sentries , & sentry )
840+ return left .CreationTime .AsTime ().Before (right .CreationTime .AsTime ())
841+ })
842+
843+ if startOffset > len (entries ) {
844+ return nil , status .Error (codes .Aborted , "starting_token is invalid" )
845+ }
846+
847+ nextPageToken := ""
848+ entries = entries [startOffset :]
849+ if req .MaxEntries > 0 && int (req .MaxEntries ) < len (entries ) {
850+ nextPageToken = strconv .Itoa (startOffset + int (req .MaxEntries ))
851+ entries = entries [:req .MaxEntries ]
835852 }
853+
836854 return & csi.ListSnapshotsResponse {
837- Entries : sentries ,
855+ Entries : entries ,
838856 NextToken : nextPageToken ,
839857 }, nil
840858}
841859
860+ func snapshotEntry (snapshot iaas.Snapshot ) * csi.ListSnapshotsResponse_Entry {
861+ ctime := timestamppb .New (* snapshot .CreatedAt )
862+ if err := ctime .CheckValid (); err != nil {
863+ klog .Errorf ("Error to convert time to timestamp: %v" , err )
864+ }
865+
866+ return & csi.ListSnapshotsResponse_Entry {
867+ Snapshot : & csi.Snapshot {
868+ SizeBytes : * snapshot .Size * util .GIBIBYTE ,
869+ SnapshotId : * snapshot .Id ,
870+ SourceVolumeId : snapshot .VolumeId ,
871+ CreationTime : ctime ,
872+ ReadyToUse : true ,
873+ },
874+ }
875+ }
876+
877+ func backupSnapshotEntry (backup iaas.Backup ) * csi.ListSnapshotsResponse_Entry {
878+ ctime := timestamppb .New (* backup .CreatedAt )
879+ if err := ctime .CheckValid (); err != nil {
880+ klog .Errorf ("Error to convert time to timestamp: %v" , err )
881+ }
882+
883+ return & csi.ListSnapshotsResponse_Entry {
884+ Snapshot : & csi.Snapshot {
885+ SizeBytes : * backup .Size * util .GIBIBYTE ,
886+ SnapshotId : * backup .Id ,
887+ SourceVolumeId : * backup .VolumeId ,
888+ CreationTime : ctime ,
889+ ReadyToUse : true ,
890+ },
891+ }
892+ }
893+
842894// ControllerGetCapabilities implements the default GRPC callout.
843895// Default supports all capabilities
844896func (cs * controllerServer ) ControllerGetCapabilities (_ context.Context , _ * csi.ControllerGetCapabilitiesRequest ) (* csi.ControllerGetCapabilitiesResponse , error ) {
0 commit comments