ceph-csi/pkg/rbd/controllerserver.go

513 lines
17 KiB
Go
Raw Normal View History

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"
"os"
2018-08-08 05:42:17 +00:00
"os/exec"
"syscall"
2018-01-09 18:59:50 +00:00
2018-12-19 14:26:16 +00:00
"github.com/ceph/ceph-csi/pkg/util"
"github.com/container-storage-interface/spec/lib/go/csi"
2018-01-09 18:59:50 +00:00
"github.com/golang/glog"
"github.com/golang/protobuf/ptypes"
"github.com/golang/protobuf/ptypes/timestamp"
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"
"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
)
// ControllerServer struct of rbd CSI driver with supported methods of CSI
// controller server spec.
type ControllerServer struct {
2018-01-09 18:59:50 +00:00
*csicommon.DefaultControllerServer
2018-12-19 14:26:16 +00:00
MetadataStore util.CachePersister
}
var (
rbdVolumes = map[string]*rbdVolume{}
rbdSnapshots = map[string]*rbdSnapshot{}
)
// LoadExDataFromMetadataStore loads the rbd volume and snapshot
// info from metadata store
func (cs *ControllerServer) LoadExDataFromMetadataStore() error {
2018-12-19 14:26:16 +00:00
vol := &rbdVolume{}
// nolint: errcheck
2018-12-19 14:26:16 +00:00
cs.MetadataStore.ForAll("csi-rbd-vol-", vol, func(identifier string) error {
rbdVolumes[identifier] = vol
return nil
})
snap := &rbdSnapshot{}
// nolint: errcheck
2018-12-19 14:26:16 +00:00
cs.MetadataStore.ForAll("csi-rbd-(.*)-snap-", snap, func(identifier string) error {
rbdSnapshots[identifier] = snap
return nil
})
2018-12-19 14:26:16 +00:00
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) validateVolumeReq(req *csi.CreateVolumeRequest) 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 err
2018-01-09 18:59:50 +00:00
}
2018-03-06 22:33:57 +00:00
// Check sanity of request Name, Volume Capabilities
if len(req.Name) == 0 {
return status.Error(codes.InvalidArgument, "Volume Name cannot be empty")
2018-03-06 22:33:57 +00:00
}
if req.VolumeCapabilities == nil {
return status.Error(codes.InvalidArgument, "Volume Capabilities cannot be empty")
2018-03-06 22:33:57 +00:00
}
return nil
}
// CreateVolume creates the volume in backend and store the volume metadata
func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
if err := cs.validateVolumeReq(req); err != nil {
return nil, err
}
volumeNameMutex.LockKey(req.GetName())
defer func() {
if err := volumeNameMutex.UnlockKey(req.GetName()); err != nil {
glog.Warningf("failed to unlock mutex volume:%s %v", req.GetName(), err)
}
}()
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 >= req.GetCapacityRange().GetRequiredBytes() {
2018-03-06 22:33:57 +00:00
// 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{
VolumeId: exVol.VolID,
CapacityBytes: exVol.VolSize,
VolumeContext: req.GetParameters(),
2018-03-06 22:33:57 +00:00
},
}, nil
}
return nil, status.Errorf(codes.AlreadyExists, "Volume with the same name: %s but with different size already exist", req.GetName())
2018-03-06 22:33:57 +00:00
}
// TODO (sbezverk) Last check for not exceeding total storage capacity
rbdVol, err := getRBDVolumeOptions(req.GetParameters())
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()
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 = req.GetCapacityRange().GetRequiredBytes()
2018-01-09 18:59:50 +00:00
}
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
found, _, _ := rbdStatus(rbdVol, rbdVol.UserID, req.GetSecrets())
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 {
if err = cs.checkSnapshot(req, rbdVol); err != nil {
2018-08-08 05:42:17 +00:00
return nil, err
}
} else {
err = createRBDImage(rbdVol, volSizeGB, rbdVol.AdminID, req.GetSecrets())
2018-01-09 18:59:50 +00:00
if err != nil {
glog.Warningf("failed to create volume: %v", err)
2018-01-09 18:59:50 +00:00
return nil, err
}
2018-08-08 05:42:17 +00:00
glog.V(4).Infof("create volume %s", volName)
2018-01-09 18:59:50 +00:00
}
}
if createErr := cs.MetadataStore.Create(volumeID, rbdVol); createErr != nil {
2018-12-19 14:26:16 +00:00
glog.Warningf("failed to store volume metadata with error: %v", err)
if err = deleteRBDImage(rbdVol, rbdVol.AdminID, req.GetSecrets()); err != nil {
2018-12-19 14:26:16 +00:00
glog.V(3).Infof("failed to delete rbd image: %s/%s with error: %v", rbdVol.Pool, rbdVol.VolName, err)
return nil, err
}
return nil, createErr
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{
VolumeId: volumeID,
CapacityBytes: volSizeBytes,
VolumeContext: req.GetParameters(),
2018-01-09 18:59:50 +00:00
},
}, nil
}
func (cs *ControllerServer) checkSnapshot(req *csi.CreateVolumeRequest, rbdVol *rbdVolume) error {
snapshot := req.VolumeContentSource.GetSnapshot()
if snapshot == nil {
return status.Error(codes.InvalidArgument, "Volume Snapshot cannot be empty")
}
snapshotID := snapshot.GetSnapshotId()
if len(snapshotID) == 0 {
return status.Error(codes.InvalidArgument, "Volume Snapshot ID cannot be empty")
}
rbdSnap := &rbdSnapshot{}
if err := cs.MetadataStore.Get(snapshotID, rbdSnap); err != nil {
return err
}
err := restoreSnapshot(rbdVol, rbdSnap, rbdVol.AdminID, req.GetSecrets())
if err != nil {
return err
}
glog.V(4).Infof("create volume %s from snapshot %s", req.GetName(), rbdSnap.SnapName)
return nil
}
// DeleteVolume deletes the volume in backend and removes the volume metadata
// from store
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
volumeID := req.GetVolumeId()
volumeIDMutex.LockKey(volumeID)
defer func() {
if err := volumeIDMutex.UnlockKey(volumeID); err != nil {
glog.Warningf("failed to unlock mutex volume:%s %v", volumeID, err)
}
}()
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 {
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)
if err := deleteRBDImage(rbdVol, rbdVol.AdminID, req.GetSecrets()); err != nil {
// 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)
return &csi.DeleteVolumeResponse{}, nil
}
// ValidateVolumeCapabilities checks whether the volume capabilities requested
// are supported.
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{Message: ""}, nil
}
}
return &csi.ValidateVolumeCapabilitiesResponse{
Confirmed: &csi.ValidateVolumeCapabilitiesResponse_Confirmed{
VolumeCapabilities: req.VolumeCapabilities,
},
}, nil
}
// ControllerUnpublishVolume returns success response
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
}
// ControllerPublishVolume returns success response
func (cs *ControllerServer) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) {
2018-01-09 18:59:50 +00:00
return &csi.ControllerPublishVolumeResponse{}, nil
}
2018-08-08 05:42:17 +00:00
// CreateSnapshot creates the snapshot in backend and stores metadata
// in store
func (cs *ControllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) {
2018-08-08 05:42:17 +00:00
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")
}
snapshotNameMutex.LockKey(req.GetName())
defer func() {
if err := snapshotNameMutex.UnlockKey(req.GetName()); err != nil {
glog.Warningf("failed to unlock mutex snapshot:%s %v", req.GetName(), err)
}
}()
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,
SnapshotId: exSnap.SnapID,
2018-08-08 05:42:17 +00:00
SourceVolumeId: exSnap.SourceVolumeID,
2018-12-05 02:44:04 +00:00
CreationTime: &timestamp.Timestamp{
Seconds: exSnap.CreatedAt,
2018-08-08 05:42:17 +00:00
},
2018-12-05 02:44:04 +00:00
ReadyToUse: true,
2018-08-08 05:42:17 +00:00
},
}, nil
}
return nil, status.Errorf(codes.AlreadyExists, "Snapshot with the same name: %s but with different source volume id already exist", req.GetName())
2018-08-08 05:42:17 +00:00
}
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.Errorf(codes.NotFound, "Source Volume ID %s cannot found", req.GetSourceVolumeId())
2018-08-08 05:42:17 +00:00
}
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
err = createSnapshot(rbdSnap, rbdSnap.AdminID, req.GetSecrets())
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)
err = protectSnapshot(rbdSnap, rbdSnap.AdminID, req.GetSecrets())
2018-08-08 05:42:17 +00:00
if err != nil {
err = deleteSnapshot(rbdSnap, rbdSnap.AdminID, req.GetSecrets())
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 = ptypes.TimestampNow().GetSeconds()
2018-08-08 05:42:17 +00:00
if createErr := cs.MetadataStore.Create(snapshotID, rbdSnap); createErr != nil {
glog.Warningf("rbd: failed to store snapInfo with error: %v", err)
// Unprotect snapshot
err = unprotectSnapshot(rbdSnap, rbdSnap.AdminID, req.GetSecrets())
if err != nil {
return nil, status.Errorf(codes.Unknown, "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.GetSecrets()); err != nil {
return nil, status.Errorf(codes.Unknown, "This Snapshot should be removed but failed to delete snapshot: %s/%s with error: %v", rbdSnap.Pool, rbdSnap.SnapName, err)
}
return nil, createErr
2018-08-08 05:42:17 +00:00
}
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,
SnapshotId: snapshotID,
2018-08-08 05:42:17 +00:00
SourceVolumeId: req.GetSourceVolumeId(),
2018-12-05 02:44:04 +00:00
CreationTime: &timestamp.Timestamp{
Seconds: rbdSnap.CreatedAt,
2018-08-08 05:42:17 +00:00
},
2018-12-05 02:44:04 +00:00
ReadyToUse: true,
2018-08-08 05:42:17 +00:00
},
}, nil
}
// DeleteSnapshot deletes the snapshot in backend and removes the
//snapshot metadata from store
func (cs *ControllerServer) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) {
2018-08-08 05:42:17 +00:00
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")
}
snapshotIDMutex.LockKey(snapshotID)
defer func() {
if err := snapshotIDMutex.UnlockKey(snapshotID); err != nil {
glog.Warningf("failed to unlock mutex snapshot:%s %v", snapshotID, err)
}
}()
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
err := unprotectSnapshot(rbdSnap, rbdSnap.AdminID, req.GetSecrets())
2018-08-08 05:42:17 +00:00
if err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "failed to unprotect snapshot: %s/%s with error: %v", rbdSnap.Pool, rbdSnap.SnapName, err)
2018-08-08 05:42:17 +00:00
}
// Deleting snapshot
glog.V(4).Infof("deleting Snaphot %s", rbdSnap.SnapName)
if err := deleteSnapshot(rbdSnap, rbdSnap.AdminID, req.GetSecrets()); err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "failed to delete snapshot: %s/%s with error: %v", rbdSnap.Pool, rbdSnap.SnapName, err)
2018-08-08 05:42:17 +00:00
}
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
}
// ListSnapshots lists the snapshots in the store
func (cs *ControllerServer) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) {
2018-08-08 05:42:17 +00:00
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()
2018-08-08 05:42:17 +00:00
// TODO (sngchlko) list with token
// 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.
if len(sourceVolumeID) != 0 && rbdSnap.SourceVolumeID != sourceVolumeID {
return nil, status.Errorf(codes.Unknown, "Requested Source Volume ID %s is different from %s", sourceVolumeID, rbdSnap.SourceVolumeID)
2018-08-08 05:42:17 +00:00
}
return &csi.ListSnapshotsResponse{
Entries: []*csi.ListSnapshotsResponse_Entry{
{
Snapshot: &csi.Snapshot{
SizeBytes: rbdSnap.SizeBytes,
SnapshotId: rbdSnap.SnapID,
2018-08-08 05:42:17 +00:00
SourceVolumeId: rbdSnap.SourceVolumeID,
2018-12-05 02:44:04 +00:00
CreationTime: &timestamp.Timestamp{
Seconds: rbdSnap.CreatedAt,
2018-08-08 05:42:17 +00:00
},
2018-12-05 02:44:04 +00:00
ReadyToUse: true,
2018-08-08 05:42:17 +00:00
},
},
},
}, nil
}
return nil, status.Errorf(codes.NotFound, "Snapshot ID %s cannot found", snapshotID)
2018-08-08 05:42:17 +00:00
}
entries := []*csi.ListSnapshotsResponse_Entry{}
for _, rbdSnap := range rbdSnapshots {
// if source volume ID also set, check source volume id on the cache.
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,
SnapshotId: rbdSnap.SnapID,
2018-08-08 05:42:17 +00:00
SourceVolumeId: rbdSnap.SourceVolumeID,
2018-12-05 02:44:04 +00:00
CreationTime: &timestamp.Timestamp{
Seconds: rbdSnap.CreatedAt,
2018-08-08 05:42:17 +00:00
},
2018-12-05 02:44:04 +00:00
ReadyToUse: true,
2018-08-08 05:42:17 +00:00
},
})
}
return &csi.ListSnapshotsResponse{
Entries: entries,
}, nil
}