2018-01-09 18:59:50 +00:00
/ *
Copyright 2018 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package rbd
import (
"fmt"
2018-10-09 10:08:56 +00:00
"os"
2018-08-08 05:42:17 +00:00
"os/exec"
"syscall"
"time"
2018-01-09 18:59:50 +00:00
2018-12-19 14:26:16 +00:00
"github.com/ceph/ceph-csi/pkg/util"
2018-03-06 22:33:57 +00:00
"github.com/container-storage-interface/spec/lib/go/csi/v0"
2018-01-09 18:59:50 +00:00
"github.com/golang/glog"
2018-03-06 22:33:57 +00:00
"github.com/kubernetes-csi/drivers/pkg/csi-common"
2018-01-09 18:59:50 +00:00
"github.com/pborman/uuid"
2018-10-09 10:08:56 +00:00
"github.com/pkg/errors"
2018-01-09 18:59:50 +00:00
"golang.org/x/net/context"
2018-03-06 22:33:57 +00:00
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
2018-01-09 18:59:50 +00:00
)
const (
oneGB = 1073741824
)
type controllerServer struct {
* csicommon . DefaultControllerServer
2018-12-19 14:26:16 +00:00
MetadataStore util . CachePersister
}
var (
rbdVolumes = map [ string ] * rbdVolume { }
rbdSnapshots = map [ string ] * rbdSnapshot { }
)
func ( cs * controllerServer ) LoadExDataFromMetadataStore ( ) error {
vol := & rbdVolume { }
cs . MetadataStore . ForAll ( "csi-rbd-vol-" , vol , func ( identifier string ) error {
rbdVolumes [ identifier ] = vol
return nil
} )
snap := & rbdSnapshot { }
cs . MetadataStore . ForAll ( "csi-rbd-(.*)-snap-" , snap , func ( identifier string ) error {
rbdSnapshots [ identifier ] = snap
return nil
} )
glog . Infof ( "Loaded %d volumes and %d snapshots from metadata store" , len ( rbdVolumes ) , len ( rbdSnapshots ) )
return nil
2018-01-09 18:59:50 +00:00
}
func ( cs * controllerServer ) CreateVolume ( ctx context . Context , req * csi . CreateVolumeRequest ) ( * csi . CreateVolumeResponse , error ) {
2018-03-06 22:33:57 +00:00
if err := cs . Driver . ValidateControllerServiceRequest ( csi . ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME ) ; err != nil {
2018-01-09 18:59:50 +00:00
glog . V ( 3 ) . Infof ( "invalid create volume req: %v" , req )
return nil , err
}
2018-03-06 22:33:57 +00:00
// Check sanity of request Name, Volume Capabilities
if len ( req . Name ) == 0 {
return nil , status . Error ( codes . InvalidArgument , "Volume Name cannot be empty" )
}
if req . VolumeCapabilities == nil {
return nil , status . Error ( codes . InvalidArgument , "Volume Capabilities cannot be empty" )
}
2018-01-16 01:52:28 +00:00
2018-10-17 12:52:45 +00:00
volumeNameMutex . LockKey ( req . GetName ( ) )
defer volumeNameMutex . UnlockKey ( req . GetName ( ) )
2018-03-06 22:33:57 +00:00
// Need to check for already existing volume name, and if found
// check for the requested capacity and already allocated capacity
if exVol , err := getRBDVolumeByName ( req . GetName ( ) ) ; err == nil {
// Since err is nil, it means the volume with the same name already exists
// need to check if the size of exisiting volume is the same as in new
// request
if exVol . VolSize >= int64 ( req . GetCapacityRange ( ) . GetRequiredBytes ( ) ) {
// exisiting volume is compatible with new request and should be reused.
// TODO (sbezverk) Do I need to make sure that RBD volume still exists?
return & csi . CreateVolumeResponse {
Volume : & csi . Volume {
Id : exVol . VolID ,
CapacityBytes : int64 ( exVol . VolSize ) ,
Attributes : req . GetParameters ( ) ,
} ,
} , nil
}
return nil , status . Error ( codes . AlreadyExists , fmt . Sprintf ( "Volume with the same name: %s but with different size already exist" , req . GetName ( ) ) )
}
// TODO (sbezverk) Last check for not exceeding total storage capacity
rbdVol , err := getRBDVolumeOptions ( req . GetParameters ( ) )
2018-01-16 01:52:28 +00:00
if err != nil {
return nil , err
}
2018-08-08 05:42:17 +00:00
// Generating Volume Name and Volume ID, as according to CSI spec they MUST be different
2018-01-09 18:59:50 +00:00
volName := req . GetName ( )
2018-01-16 01:52:28 +00:00
uniqueID := uuid . NewUUID ( ) . String ( )
2018-01-09 18:59:50 +00:00
if len ( volName ) == 0 {
2018-03-06 22:33:57 +00:00
volName = rbdVol . Pool + "-dynamic-pvc-" + uniqueID
2018-01-09 18:59:50 +00:00
}
2018-03-06 22:33:57 +00:00
rbdVol . VolName = volName
2018-12-19 14:26:16 +00:00
volumeID := "csi-rbd-vol-" + uniqueID
2018-03-06 22:33:57 +00:00
rbdVol . VolID = volumeID
2018-01-09 18:59:50 +00:00
// Volume Size - Default is 1 GiB
volSizeBytes := int64 ( oneGB )
if req . GetCapacityRange ( ) != nil {
volSizeBytes = int64 ( req . GetCapacityRange ( ) . GetRequiredBytes ( ) )
}
2018-03-06 22:33:57 +00:00
rbdVol . VolSize = volSizeBytes
2018-01-09 18:59:50 +00:00
volSizeGB := int ( volSizeBytes / 1024 / 1024 / 1024 )
// Check if there is already RBD image with requested name
2018-08-09 13:07:00 +00:00
found , _ , _ := rbdStatus ( rbdVol , rbdVol . UserId , req . GetControllerCreateSecrets ( ) )
2018-01-09 18:59:50 +00:00
if ! found {
2018-08-08 05:42:17 +00:00
// if VolumeContentSource is not nil, this request is for snapshot
if req . VolumeContentSource != nil {
snapshot := req . VolumeContentSource . GetSnapshot ( )
if snapshot == nil {
return nil , status . Error ( codes . InvalidArgument , "Volume Snapshot cannot be empty" )
}
snapshotID := snapshot . GetId ( )
2018-08-09 13:06:51 +00:00
if len ( snapshotID ) == 0 {
2018-08-08 05:42:17 +00:00
return nil , status . Error ( codes . InvalidArgument , "Volume Snapshot ID cannot be empty" )
}
rbdSnap := & rbdSnapshot { }
2018-12-19 14:26:16 +00:00
if err := cs . MetadataStore . Get ( snapshotID , rbdSnap ) ; err != nil {
2018-08-08 05:42:17 +00:00
return nil , err
}
2018-08-09 13:07:00 +00:00
err = restoreSnapshot ( rbdVol , rbdSnap , rbdVol . AdminId , req . GetControllerCreateSecrets ( ) )
2018-01-09 18:59:50 +00:00
if err != nil {
return nil , err
}
2018-08-08 05:42:17 +00:00
glog . V ( 4 ) . Infof ( "create volume %s from snapshot %s" , volName , rbdSnap . SnapName )
} else {
2018-08-09 13:07:00 +00:00
if err := createRBDImage ( rbdVol , volSizeGB , rbdVol . AdminId , req . GetControllerCreateSecrets ( ) ) ; err != nil {
2018-08-08 05:42:17 +00:00
if err != nil {
glog . Warningf ( "failed to create volume: %v" , err )
return nil , err
}
}
glog . V ( 4 ) . Infof ( "create volume %s" , volName )
2018-01-09 18:59:50 +00:00
}
}
2018-12-19 14:26:16 +00:00
if err := cs . MetadataStore . Create ( volumeID , rbdVol ) ; err != nil {
glog . Warningf ( "failed to store volume metadata with error: %v" , err )
if err := deleteRBDImage ( rbdVol , rbdVol . AdminId , req . GetControllerCreateSecrets ( ) ) ; err != nil {
glog . V ( 3 ) . Infof ( "failed to delete rbd image: %s/%s with error: %v" , rbdVol . Pool , rbdVol . VolName , err )
return nil , err
}
return nil , err
2018-01-09 18:59:50 +00:00
}
2018-12-19 14:26:16 +00:00
2018-08-09 13:06:51 +00:00
rbdVolumes [ volumeID ] = rbdVol
2018-01-09 18:59:50 +00:00
return & csi . CreateVolumeResponse {
2018-02-15 13:51:23 +00:00
Volume : & csi . Volume {
2018-01-16 01:52:28 +00:00
Id : volumeID ,
2018-02-15 13:51:23 +00:00
CapacityBytes : int64 ( volSizeBytes ) ,
2018-01-09 18:59:50 +00:00
Attributes : req . GetParameters ( ) ,
} ,
} , nil
}
func ( cs * controllerServer ) DeleteVolume ( ctx context . Context , req * csi . DeleteVolumeRequest ) ( * csi . DeleteVolumeResponse , error ) {
2018-03-06 22:33:57 +00:00
if err := cs . Driver . ValidateControllerServiceRequest ( csi . ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME ) ; err != nil {
2018-01-09 18:59:50 +00:00
glog . Warningf ( "invalid delete volume req: %v" , req )
return nil , err
}
// For now the image get unconditionally deleted, but here retention policy can be checked
2018-01-16 01:52:28 +00:00
volumeID := req . GetVolumeId ( )
2018-10-17 12:52:45 +00:00
volumeIDMutex . LockKey ( volumeID )
defer volumeIDMutex . UnlockKey ( volumeID )
2018-12-19 14:26:16 +00:00
2018-03-06 22:33:57 +00:00
rbdVol := & rbdVolume { }
2018-12-19 14:26:16 +00:00
if err := cs . MetadataStore . Get ( volumeID , rbdVol ) ; err != nil {
2018-10-09 10:08:56 +00:00
if os . IsNotExist ( errors . Cause ( err ) ) {
return & csi . DeleteVolumeResponse { } , nil
}
2018-01-09 18:59:50 +00:00
return nil , err
}
2018-12-19 14:26:16 +00:00
2018-03-06 22:33:57 +00:00
volName := rbdVol . VolName
2018-01-09 18:59:50 +00:00
// Deleting rbd image
glog . V ( 4 ) . Infof ( "deleting volume %s" , volName )
2018-08-09 13:07:00 +00:00
if err := deleteRBDImage ( rbdVol , rbdVol . AdminId , req . GetControllerDeleteSecrets ( ) ) ; err != nil {
2018-10-09 10:08:56 +00:00
// TODO: can we detect "already deleted" situations here and proceed?
2018-03-06 22:33:57 +00:00
glog . V ( 3 ) . Infof ( "failed to delete rbd image: %s/%s with error: %v" , rbdVol . Pool , volName , err )
2018-01-09 18:59:50 +00:00
return nil , err
}
2018-12-19 14:26:16 +00:00
if err := cs . MetadataStore . Delete ( volumeID ) ; err != nil {
2018-01-09 18:59:50 +00:00
return nil , err
}
2018-03-06 22:33:57 +00:00
delete ( rbdVolumes , volumeID )
2018-01-18 19:13:08 +00:00
return & csi . DeleteVolumeResponse { } , nil
}
func ( cs * controllerServer ) ValidateVolumeCapabilities ( ctx context . Context , req * csi . ValidateVolumeCapabilitiesRequest ) ( * csi . ValidateVolumeCapabilitiesResponse , error ) {
for _ , cap := range req . VolumeCapabilities {
if cap . GetAccessMode ( ) . GetMode ( ) != csi . VolumeCapability_AccessMode_SINGLE_NODE_WRITER {
return & csi . ValidateVolumeCapabilitiesResponse { Supported : false , Message : "" } , nil
}
}
return & csi . ValidateVolumeCapabilitiesResponse { Supported : true , Message : "" } , nil
}
func ( cs * controllerServer ) ControllerUnpublishVolume ( ctx context . Context , req * csi . ControllerUnpublishVolumeRequest ) ( * csi . ControllerUnpublishVolumeResponse , error ) {
2018-01-09 18:59:50 +00:00
return & csi . ControllerUnpublishVolumeResponse { } , nil
}
func ( cs * controllerServer ) ControllerPublishVolume ( ctx context . Context , req * csi . ControllerPublishVolumeRequest ) ( * csi . ControllerPublishVolumeResponse , error ) {
return & csi . ControllerPublishVolumeResponse { } , nil
}
2018-08-08 05:42:17 +00:00
func ( cs * controllerServer ) CreateSnapshot ( ctx context . Context , req * csi . CreateSnapshotRequest ) ( * csi . CreateSnapshotResponse , error ) {
if err := cs . Driver . ValidateControllerServiceRequest ( csi . ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT ) ; err != nil {
glog . Warningf ( "invalid create snapshot req: %v" , req )
return nil , err
}
// Check sanity of request Snapshot Name, Source Volume Id
if len ( req . Name ) == 0 {
return nil , status . Error ( codes . InvalidArgument , "Snapshot Name cannot be empty" )
}
if len ( req . SourceVolumeId ) == 0 {
return nil , status . Error ( codes . InvalidArgument , "Source Volume ID cannot be empty" )
}
2018-10-17 12:52:45 +00:00
snapshotNameMutex . LockKey ( req . GetName ( ) )
defer snapshotNameMutex . UnlockKey ( req . GetName ( ) )
2018-08-08 05:42:17 +00:00
// Need to check for already existing snapshot name, and if found
// check for the requested source volume id and already allocated source volume id
if exSnap , err := getRBDSnapshotByName ( req . GetName ( ) ) ; err == nil {
if req . SourceVolumeId == exSnap . SourceVolumeID {
return & csi . CreateSnapshotResponse {
Snapshot : & csi . Snapshot {
SizeBytes : exSnap . SizeBytes ,
Id : exSnap . SnapID ,
SourceVolumeId : exSnap . SourceVolumeID ,
CreatedAt : exSnap . CreatedAt ,
Status : & csi . SnapshotStatus {
Type : csi . SnapshotStatus_READY ,
} ,
} ,
} , nil
}
return nil , status . Error ( codes . AlreadyExists , fmt . Sprintf ( "Snapshot with the same name: %s but with different source volume id already exist" , req . GetName ( ) ) )
}
rbdSnap , err := getRBDSnapshotOptions ( req . GetParameters ( ) )
if err != nil {
return nil , err
}
// Generating Snapshot Name and Snapshot ID, as according to CSI spec they MUST be different
snapName := req . GetName ( )
uniqueID := uuid . NewUUID ( ) . String ( )
rbdVolume , err := getRBDVolumeByID ( req . GetSourceVolumeId ( ) )
if err != nil {
return nil , status . Error ( codes . NotFound , fmt . Sprintf ( "Source Volume ID %s cannot found" , req . GetSourceVolumeId ( ) ) )
}
2018-08-09 13:07:13 +00:00
if ! hasSnapshotFeature ( rbdVolume . ImageFeatures ) {
return nil , fmt . Errorf ( "Volume(%s) has not snapshot feature(layering)" , req . GetSourceVolumeId ( ) )
}
2018-08-08 05:42:17 +00:00
rbdSnap . VolName = rbdVolume . VolName
rbdSnap . SnapName = snapName
2018-08-09 13:06:51 +00:00
snapshotID := "csi-rbd-" + rbdVolume . VolName + "-snap-" + uniqueID
2018-08-08 05:42:17 +00:00
rbdSnap . SnapID = snapshotID
rbdSnap . SourceVolumeID = req . GetSourceVolumeId ( )
rbdSnap . SizeBytes = rbdVolume . VolSize
2018-08-09 13:07:00 +00:00
err = createSnapshot ( rbdSnap , rbdSnap . AdminId , req . GetCreateSnapshotSecrets ( ) )
2018-08-08 05:42:17 +00:00
// if we already have the snapshot, return the snapshot
if err != nil {
if exitErr , ok := err . ( * exec . ExitError ) ; ok {
if status , ok := exitErr . Sys ( ) . ( syscall . WaitStatus ) ; ok {
if status . ExitStatus ( ) == int ( syscall . EEXIST ) {
glog . Warningf ( "Snapshot with the same name: %s, we return this." , req . GetName ( ) )
} else {
glog . Warningf ( "failed to create snapshot: %v" , err )
return nil , err
}
} else {
glog . Warningf ( "failed to create snapshot: %v" , err )
return nil , err
}
} else {
glog . Warningf ( "failed to create snapshot: %v" , err )
return nil , err
}
} else {
glog . V ( 4 ) . Infof ( "create snapshot %s" , snapName )
2018-08-09 13:07:00 +00:00
err = protectSnapshot ( rbdSnap , rbdSnap . AdminId , req . GetCreateSnapshotSecrets ( ) )
2018-08-08 05:42:17 +00:00
if err != nil {
2018-08-09 13:07:00 +00:00
err = deleteSnapshot ( rbdSnap , rbdSnap . AdminId , req . GetCreateSnapshotSecrets ( ) )
2018-08-08 05:42:17 +00:00
if err != nil {
return nil , fmt . Errorf ( "snapshot is created but failed to protect and delete snapshot: %v" , err )
}
return nil , fmt . Errorf ( "Snapshot is created but failed to protect snapshot" )
}
}
rbdSnap . CreatedAt = time . Now ( ) . UnixNano ( )
2018-12-19 14:26:16 +00:00
if err := cs . MetadataStore . Create ( snapshotID , rbdSnap ) ; err != nil {
2018-08-09 13:07:06 +00:00
glog . Warningf ( "rbd: failed to store snapInfo with error: %v" , err )
// Unprotect snapshot
err := unprotectSnapshot ( rbdSnap , rbdSnap . AdminId , req . GetCreateSnapshotSecrets ( ) )
if err != nil {
return nil , status . Error ( codes . Unknown , fmt . Sprintf ( "This Snapshot should be removed but failed to unprotect snapshot: %s/%s with error: %v" , rbdSnap . Pool , rbdSnap . SnapName , err ) )
}
// Deleting snapshot
glog . V ( 4 ) . Infof ( "deleting Snaphot %s" , rbdSnap . SnapName )
if err := deleteSnapshot ( rbdSnap , rbdSnap . AdminId , req . GetCreateSnapshotSecrets ( ) ) ; err != nil {
return nil , status . Error ( codes . Unknown , fmt . Sprintf ( "This Snapshot should be removed but failed to delete snapshot: %s/%s with error: %v" , rbdSnap . Pool , rbdSnap . SnapName , err ) )
}
2018-08-08 05:42:17 +00:00
return nil , err
}
2018-12-19 14:26:16 +00:00
2018-08-09 13:06:51 +00:00
rbdSnapshots [ snapshotID ] = rbdSnap
2018-08-08 05:42:17 +00:00
return & csi . CreateSnapshotResponse {
Snapshot : & csi . Snapshot {
SizeBytes : rbdSnap . SizeBytes ,
Id : snapshotID ,
SourceVolumeId : req . GetSourceVolumeId ( ) ,
CreatedAt : rbdSnap . CreatedAt ,
Status : & csi . SnapshotStatus {
Type : csi . SnapshotStatus_READY ,
} ,
} ,
} , nil
}
func ( cs * controllerServer ) DeleteSnapshot ( ctx context . Context , req * csi . DeleteSnapshotRequest ) ( * csi . DeleteSnapshotResponse , error ) {
if err := cs . Driver . ValidateControllerServiceRequest ( csi . ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT ) ; err != nil {
glog . Warningf ( "invalid delete snapshot req: %v" , req )
return nil , err
}
snapshotID := req . GetSnapshotId ( )
2018-08-09 13:06:51 +00:00
if len ( snapshotID ) == 0 {
2018-08-08 05:42:17 +00:00
return nil , status . Error ( codes . InvalidArgument , "Snapshot ID cannot be empty" )
}
2018-10-17 12:52:45 +00:00
snapshotIDMutex . LockKey ( snapshotID )
defer snapshotIDMutex . UnlockKey ( snapshotID )
2018-08-08 05:42:17 +00:00
rbdSnap := & rbdSnapshot { }
2018-12-19 14:26:16 +00:00
if err := cs . MetadataStore . Get ( snapshotID , rbdSnap ) ; err != nil {
2018-08-08 05:42:17 +00:00
return nil , err
}
// Unprotect snapshot
2018-08-09 13:07:00 +00:00
err := unprotectSnapshot ( rbdSnap , rbdSnap . AdminId , req . GetDeleteSnapshotSecrets ( ) )
2018-08-08 05:42:17 +00:00
if err != nil {
return nil , status . Error ( codes . FailedPrecondition , fmt . Sprintf ( "failed to unprotect snapshot: %s/%s with error: %v" , rbdSnap . Pool , rbdSnap . SnapName , err ) )
}
// Deleting snapshot
glog . V ( 4 ) . Infof ( "deleting Snaphot %s" , rbdSnap . SnapName )
2018-08-09 13:07:00 +00:00
if err := deleteSnapshot ( rbdSnap , rbdSnap . AdminId , req . GetDeleteSnapshotSecrets ( ) ) ; err != nil {
2018-08-08 05:42:17 +00:00
return nil , status . Error ( codes . FailedPrecondition , fmt . Sprintf ( "failed to delete snapshot: %s/%s with error: %v" , rbdSnap . Pool , rbdSnap . SnapName , err ) )
}
2018-12-19 14:26:16 +00:00
if err := cs . MetadataStore . Delete ( snapshotID ) ; err != nil {
2018-08-08 05:42:17 +00:00
return nil , err
}
delete ( rbdSnapshots , snapshotID )
return & csi . DeleteSnapshotResponse { } , nil
}
func ( cs * controllerServer ) ListSnapshots ( ctx context . Context , req * csi . ListSnapshotsRequest ) ( * csi . ListSnapshotsResponse , error ) {
if err := cs . Driver . ValidateControllerServiceRequest ( csi . ControllerServiceCapability_RPC_LIST_SNAPSHOTS ) ; err != nil {
glog . Warningf ( "invalid list snapshot req: %v" , req )
return nil , err
}
sourceVolumeId := req . GetSourceVolumeId ( )
// TODO (sngchlko) list with token
2018-10-17 12:52:45 +00:00
// TODO (#94) protect concurrent access to global data structures
2018-08-08 05:42:17 +00:00
// list only a specific snapshot which has snapshot ID
2018-08-09 13:06:51 +00:00
if snapshotID := req . GetSnapshotId ( ) ; len ( snapshotID ) != 0 {
2018-08-08 05:42:17 +00:00
if rbdSnap , ok := rbdSnapshots [ snapshotID ] ; ok {
// if source volume ID also set, check source volume id on the cache.
2018-08-09 13:06:51 +00:00
if len ( sourceVolumeId ) != 0 && rbdSnap . SourceVolumeID != sourceVolumeId {
2018-08-08 05:42:17 +00:00
return nil , status . Error ( codes . Unknown , fmt . Sprintf ( "Requested Source Volume ID %s is different from %s" , sourceVolumeId , rbdSnap . SourceVolumeID ) )
}
return & csi . ListSnapshotsResponse {
Entries : [ ] * csi . ListSnapshotsResponse_Entry {
{
Snapshot : & csi . Snapshot {
SizeBytes : rbdSnap . SizeBytes ,
Id : rbdSnap . SnapID ,
SourceVolumeId : rbdSnap . SourceVolumeID ,
CreatedAt : rbdSnap . CreatedAt ,
Status : & csi . SnapshotStatus {
Type : csi . SnapshotStatus_READY ,
} ,
} ,
} ,
} ,
} , nil
} else {
return nil , status . Error ( codes . NotFound , fmt . Sprintf ( "Snapshot ID %s cannot found" , snapshotID ) )
}
}
entries := [ ] * csi . ListSnapshotsResponse_Entry { }
for _ , rbdSnap := range rbdSnapshots {
// if source volume ID also set, check source volume id on the cache.
2018-08-09 13:06:51 +00:00
if len ( sourceVolumeId ) != 0 && rbdSnap . SourceVolumeID != sourceVolumeId {
2018-08-08 05:42:17 +00:00
continue
}
entries = append ( entries , & csi . ListSnapshotsResponse_Entry {
Snapshot : & csi . Snapshot {
SizeBytes : rbdSnap . SizeBytes ,
Id : rbdSnap . SnapID ,
SourceVolumeId : rbdSnap . SourceVolumeID ,
CreatedAt : rbdSnap . CreatedAt ,
Status : & csi . SnapshotStatus {
Type : csi . SnapshotStatus_READY ,
} ,
} ,
} )
}
return & csi . ListSnapshotsResponse {
Entries : entries ,
} , nil
}