Skip to content

Commit 2bdbd8a

Browse files
authored
[manila-csi-plugin] snapshot caps check (kubernetes#1592)
* go.mod: bumped github.com/gophercloud/gophercloud to v0.19.0 * removed hardcoded checks for cephfs, added checks for caps in parent share This commit removes all hard-coded checks for CephFS shares, preventing them from being snapshotted. Instead, manila-csi now checks for snapshot_support and create_share_from_snapshot capabilities. * share: added handling for creating_from_snapshot status * docs: added a note about snapshots * go.sum: added k8s.io/code-generator * go.mod fix 2: added github.com/gophercloud/gophercloud v0.19.0
1 parent b2d6dab commit 2bdbd8a

File tree

7 files changed

+66
-72
lines changed

7 files changed

+66
-72
lines changed

docs/manila-csi-plugin/using-manila-csi-plugin.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121

2222
# CSI Manila driver
2323

24-
The CSI Manila driver is able to create, expand and mount OpenStack Manila shares. Snapshots and recovering shares from snapshots is supported as well (support for CephFS snapshots will be added soon).
24+
The CSI Manila driver is able to create, expand, snapshot, restore and mount OpenStack Manila shares.
25+
26+
Currently supported Manila backends are NFS and native CephFS.
2527

2628
## Configuration
2729

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/MichaelTJones/walk v0.0.0-20161122175330-4748e29d5718 // indirect
77
github.com/container-storage-interface/spec v1.3.0
88
github.com/golang/protobuf v1.4.3
9-
github.com/gophercloud/gophercloud v0.15.1-0.20210105012856-e34a44dc6580
9+
github.com/gophercloud/gophercloud v0.19.0
1010
github.com/gophercloud/utils v0.0.0-20200423144003-7c72efc7435d
1111
github.com/gorilla/mux v1.8.0
1212
github.com/hashicorp/go-version v1.2.0

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,8 @@ github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEo
426426
github.com/gophercloud/gophercloud v0.6.1-0.20191122030953-d8ac278c1c9d/go.mod h1:ozGNgr9KYOVATV5jsgHl/ceCDXGuguqOZAzoQ/2vcNM=
427427
github.com/gophercloud/gophercloud v0.15.1-0.20210105012856-e34a44dc6580 h1:EWFhxn19tw6t3LjFe95Kli30sBI02+fPYBqMvXmCvtU=
428428
github.com/gophercloud/gophercloud v0.15.1-0.20210105012856-e34a44dc6580/go.mod h1:VX0Ibx85B60B5XOrZr6kaNwrmPUzcmMpwxvQ1WQIIWM=
429+
github.com/gophercloud/gophercloud v0.19.0 h1:zzaIh8W2K5M4AkJhPV1z6O4Sp0FOObzXm61NUmFz3Kw=
430+
github.com/gophercloud/gophercloud v0.19.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
429431
github.com/gophercloud/utils v0.0.0-20200423144003-7c72efc7435d h1:fduaPzWwIfvOMLuHk2Al3GZH0XbUqG8MbElPop+Igzs=
430432
github.com/gophercloud/utils v0.0.0-20200423144003-7c72efc7435d/go.mod h1:ehWUbLQJPqS0Ep+CxeD559hsm9pthPXadJNKwZkp43w=
431433
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
@@ -884,6 +886,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
884886
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
885887
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
886888
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
889+
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
887890
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
888891
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
889892
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=

pkg/csi/manila/controllerserver.go

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ var (
4545
pendingSnapshots = sync.Map{}
4646
)
4747

48-
func getVolumeCreator(source *csi.VolumeContentSource, shareOpts *options.ControllerVolumeContext, compatOpts *options.CompatibilityOptions, shareTypeCaps capabilities.ManilaCapabilities) (volumeCreator, error) {
48+
func getVolumeCreator(source *csi.VolumeContentSource, shareOpts *options.ControllerVolumeContext, compatOpts *options.CompatibilityOptions) (volumeCreator, error) {
4949
if source == nil {
5050
return &blankVolume{}, nil
5151
}
@@ -55,11 +55,6 @@ func getVolumeCreator(source *csi.VolumeContentSource, shareOpts *options.Contro
5555
}
5656

5757
if source.GetSnapshot() != nil {
58-
if tryCompatForVolumeSource(compatOpts, shareOpts, source, shareTypeCaps) != nil {
59-
klog.Infof("share type %s does not advertise create_share_from_snapshot_support capability, compatibility mode is available", shareOpts.Type)
60-
return &blankVolume{}, nil
61-
}
62-
6358
return &volumeFromSnapshot{}, nil
6459
}
6560

@@ -121,7 +116,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
121116

122117
// Retrieve an existing share or create a new one
123118

124-
volCreator, err := getVolumeCreator(req.GetVolumeContentSource(), shareOpts, cs.d.compatOpts, shareTypeCaps)
119+
volCreator, err := getVolumeCreator(req.GetVolumeContentSource(), shareOpts, cs.d.compatOpts)
125120
if err != nil {
126121
return nil, err
127122
}
@@ -148,15 +143,6 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
148143
return nil, status.Errorf(codes.Internal, "failed to grant access to volume %s: %v", share.Name, err)
149144
}
150145

151-
// Check if compatibility layer is needed and can be used
152-
if compatLayer := tryCompatForVolumeSource(cs.d.compatOpts, shareOpts, req.GetVolumeContentSource(), shareTypeCaps); compatLayer != nil {
153-
if err = compatLayer.SupplementCapability(cs.d.compatOpts, share, accessRight, req, cs.d.fwdEndpoint, manilaClient, cs.d.csiClientBuilder); err != nil {
154-
// An error occurred, the user must clean the share manually
155-
// TODO needs proper monitoring
156-
return nil, err
157-
}
158-
}
159-
160146
var accessibleTopology []*csi.Topology
161147
if cs.d.withTopology {
162148
// All requisite/preferred topologies are considered valid. Nodes from those zones are required to be able to reach the storage.
@@ -202,12 +188,6 @@ func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVol
202188
}
203189

204190
func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) {
205-
if cs.d.shareProto == "CEPHFS" {
206-
// Restoring shares from CephFS snapshots needs special handling that's not implemented yet.
207-
// TODO: Creating CephFS snapshots is forbidden until CephFS restoration is in place.
208-
return nil, status.Errorf(codes.InvalidArgument, "the driver doesn't support snapshotting CephFS shares yet")
209-
}
210-
211191
if err := validateCreateSnapshotRequest(req); err != nil {
212192
return nil, status.Error(codes.InvalidArgument, err.Error())
213193
}
@@ -246,6 +226,17 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS
246226
sourceShare.ShareProto, req.GetSourceVolumeId(), cs.d.shareProto)
247227
}
248228

229+
// In order to satisfy CSI spec requirements around CREATE_DELETE_SNAPSHOT
230+
// and the ability to populate volumes with snapshot contents, parent share
231+
// must advertise snapshot_support and create_share_from_snapshot_support
232+
// capabilities.
233+
234+
if !sourceShare.SnapshotSupport || !sourceShare.CreateShareFromSnapshotSupport {
235+
return nil, status.Errorf(codes.InvalidArgument,
236+
"cannot create snapshot %s for volume %s: parent share must advertise snapshot_support and create_share_from_snapshot_support capabilities",
237+
req.GetName(), req.GetSourceVolumeId())
238+
}
239+
249240
// Retrieve an existing snapshot or create a new one
250241

251242
snapshot, err := getOrCreateSnapshot(req.GetName(), sourceShare.ID, manilaClient)
@@ -258,11 +249,11 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS
258249
return nil, status.Errorf(codes.NotFound, "failed to create snapshot %s for volume %s because the volume doesn't exist: %v", req.GetName(), req.GetSourceVolumeId(), err)
259250
}
260251

261-
return nil, status.Errorf(codes.Internal, "failed to create a snapshot %s of volume %s: %v", req.GetName(), req.GetSourceVolumeId(), err)
252+
return nil, status.Errorf(codes.Internal, "failed to create snapshot %s of volume %s: %v", req.GetName(), req.GetSourceVolumeId(), err)
262253
}
263254

264255
if err = verifySnapshotCompatibility(snapshot, req); err != nil {
265-
return nil, status.Errorf(codes.AlreadyExists, "a snapshot named %s already exists, but is incompatible with the request: %v", req.GetName(), err)
256+
return nil, status.Errorf(codes.AlreadyExists, "snapshot %s already exists, but is incompatible with the request: %v", req.GetName(), err)
266257
}
267258

268259
// Check for snapshot status, determine whether it's ready
@@ -307,12 +298,6 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS
307298
}
308299

309300
func (cs *controllerServer) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) {
310-
if cs.d.shareProto == "CEPHFS" {
311-
// Restoring shares from CephFS snapshots needs special handling that's not implemented yet.
312-
// TODO: Deleting CephFS snapshots is forbidden until CephFS restoration is in place.
313-
return nil, status.Errorf(codes.InvalidArgument, "the driver doesn't support CephFS snapshots yet")
314-
}
315-
316301
if err := validateDeleteSnapshotRequest(req); err != nil {
317302
return nil, status.Error(codes.InvalidArgument, err.Error())
318303
}

pkg/csi/manila/share.go

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,31 @@ const (
3333
waitForAvailableShareTimeout = 3
3434
waitForAvailableShareRetries = 10
3535

36-
shareCreating = "creating"
37-
shareDeleting = "deleting"
38-
shareExtending = "extending"
39-
shareError = "error"
40-
shareErrorDeleting = "error_deleting"
41-
shareAvailable = "available"
36+
shareCreating = "creating"
37+
shareCreatingFromSnapshot = "creating_from_snapshot"
38+
shareDeleting = "deleting"
39+
shareExtending = "extending"
40+
shareError = "error"
41+
shareErrorDeleting = "error_deleting"
42+
shareErrorExtending = "extending_error"
43+
shareAvailable = "available"
4244

4345
shareDescription = "provisioned-by=manila.csi.openstack.org"
4446
)
4547

48+
var (
49+
shareErrorStatuses = map[string]struct{}{
50+
shareError: {},
51+
shareErrorDeleting: {},
52+
shareErrorExtending: {},
53+
}
54+
)
55+
56+
func isShareInErrorState(s string) bool {
57+
_, found := shareErrorStatuses[s]
58+
return found
59+
}
60+
4661
// getOrCreateShare first retrieves an existing share with name=shareName, or creates a new one if it doesn't exist yet.
4762
// Once the share is created, an exponential back-off is used to wait till the status of the share is "available".
4863
func getOrCreateShare(shareName string, createOpts *shares.CreateOpts, manilaClient manilaclient.Interface) (*shares.Share, manilaError, error) {
@@ -75,7 +90,7 @@ func getOrCreateShare(shareName string, createOpts *shares.CreateOpts, manilaCli
7590
return share, 0, nil
7691
}
7792

78-
return waitForShareStatus(share.ID, shareCreating, shareAvailable, false, manilaClient)
93+
return waitForShareStatus(share.ID, []string{shareCreating, shareCreatingFromSnapshot}, shareAvailable, false, manilaClient)
7994
}
8095

8196
func deleteShare(shareID string, manilaClient manilaclient.Interface) error {
@@ -101,7 +116,7 @@ func tryDeleteShare(share *shares.Share, manilaClient manilaclient.Interface) {
101116
return
102117
}
103118

104-
_, _, err := waitForShareStatus(share.ID, shareDeleting, "", true, manilaClient)
119+
_, _, err := waitForShareStatus(share.ID, []string{shareDeleting}, "", true, manilaClient)
105120
if err != nil && err != wait.ErrWaitTimeout {
106121
klog.Errorf("couldn't retrieve volume %s in a roll-back procedure: %v", share.Name, err)
107122
}
@@ -116,7 +131,7 @@ func extendShare(share *shares.Share, newSizeInGiB int, manilaClient manilaclien
116131
return err
117132
}
118133

119-
share, manilaErrCode, err := waitForShareStatus(share.ID, shareExtending, shareAvailable, false, manilaClient)
134+
share, manilaErrCode, err := waitForShareStatus(share.ID, []string{shareExtending}, shareAvailable, false, manilaClient)
120135
if err != nil {
121136
if err == wait.ErrWaitTimeout {
122137
return status.Errorf(codes.DeadlineExceeded, "deadline exceeded while waiting for volume ID %s to become available", share.Name)
@@ -128,7 +143,7 @@ func extendShare(share *shares.Share, newSizeInGiB int, manilaClient manilaclien
128143
return nil
129144
}
130145

131-
func waitForShareStatus(shareID, currentStatus, desiredStatus string, successOnNotFound bool, manilaClient manilaclient.Interface) (*shares.Share, manilaError, error) {
146+
func waitForShareStatus(shareID string, validTransientStates []string, desiredStatus string, successOnNotFound bool, manilaClient manilaclient.Interface) (*shares.Share, manilaError, error) {
132147
var (
133148
backoff = wait.Backoff{
134149
Duration: time.Second * waitForAvailableShareTimeout,
@@ -141,6 +156,15 @@ func waitForShareStatus(shareID, currentStatus, desiredStatus string, successOnN
141156
err error
142157
)
143158

159+
isInTransientState := func(s string) bool {
160+
for _, val := range validTransientStates {
161+
if s == val {
162+
return true
163+
}
164+
}
165+
return false
166+
}
167+
144168
return share, manilaErrCode, wait.ExponentialBackoff(backoff, func() (bool, error) {
145169
share, err = manilaClient.GetShareByID(shareID)
146170

@@ -152,25 +176,24 @@ func waitForShareStatus(shareID, currentStatus, desiredStatus string, successOnN
152176
return false, err
153177
}
154178

155-
var isAvailable bool
179+
if share.Status == desiredStatus {
180+
return true, nil
181+
}
182+
183+
if isInTransientState(share.Status) {
184+
return false, nil
185+
}
156186

157-
switch share.Status {
158-
case currentStatus:
159-
isAvailable = false
160-
case desiredStatus:
161-
isAvailable = true
162-
case shareError:
187+
if isShareInErrorState(share.Status) {
163188
manilaErrMsg, err := lastResourceError(shareID, manilaClient)
164189
if err != nil {
165190
return false, fmt.Errorf("share %s is in error state, error description could not be retrieved: %v", shareID, err)
166191
}
167192

168193
manilaErrCode = manilaErrMsg.errCode
169-
return false, fmt.Errorf("share %s is in error state: %s", shareID, manilaErrMsg.message)
170-
default:
171-
return false, fmt.Errorf("share %s is in an unexpected state: wanted either %s or %s, got %s", shareID, currentStatus, desiredStatus, share.Status)
194+
return false, fmt.Errorf("share %s is in error state \"%s\": %s", shareID, share.Status, manilaErrMsg.message)
172195
}
173196

174-
return isAvailable, nil
197+
return false, fmt.Errorf("share %s is in an unexpected state: wanted either %v or %s, got %s", shareID, validTransientStates, desiredStatus, share.Status)
175198
})
176199
}

pkg/csi/manila/util.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package manila
1919
import (
2020
"errors"
2121
"fmt"
22-
"strconv"
2322
"strings"
2423

2524
"github.com/container-storage-interface/spec/lib/go/csi"
@@ -28,7 +27,6 @@ import (
2827
"github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots"
2928
"google.golang.org/grpc/codes"
3029
"k8s.io/cloud-provider-openstack/pkg/csi/manila/capabilities"
31-
"k8s.io/cloud-provider-openstack/pkg/csi/manila/compatibility"
3230
"k8s.io/cloud-provider-openstack/pkg/csi/manila/manilaclient"
3331
"k8s.io/cloud-provider-openstack/pkg/csi/manila/options"
3432
"k8s.io/klog/v2"
@@ -156,18 +154,6 @@ func compareProtocol(protoA, protoB string) bool {
156154
return strings.ToUpper(protoA) == strings.ToUpper(protoB)
157155
}
158156

159-
func tryCompatForVolumeSource(compatOpts *options.CompatibilityOptions, shareOpts *options.ControllerVolumeContext, source *csi.VolumeContentSource, shareTypeCaps capabilities.ManilaCapabilities) compatibility.Layer {
160-
if source != nil {
161-
if source.GetSnapshot() != nil && shareTypeCaps[capabilities.ManilaCapabilitySnapshot] {
162-
if createShareFromSnapshotEnabled, _ := strconv.ParseBool(compatOpts.CreateShareFromSnapshotEnabled); createShareFromSnapshotEnabled {
163-
return compatibility.FindCompatibilityLayer(shareOpts.Protocol, capabilities.ManilaCapabilityShareFromSnapshot, shareTypeCaps)
164-
}
165-
}
166-
}
167-
168-
return nil
169-
}
170-
171157
//
172158
// Controller service request validation
173159
//

pkg/csi/manila/volumesource.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,6 @@ type volumeFromSnapshot struct{}
6767
func (volumeFromSnapshot) create(req *csi.CreateVolumeRequest, shareName string, sizeInGiB int, manilaClient manilaclient.Interface, shareOpts *options.ControllerVolumeContext, shareMetadata map[string]string) (*shares.Share, error) {
6868
snapshotSource := req.GetVolumeContentSource().GetSnapshot()
6969

70-
if shareOpts.Protocol == "CEPHFS" {
71-
// TODO: Restoring shares from CephFS snapshots needs special handling.
72-
return nil, status.Errorf(codes.InvalidArgument, "restoring CephFS snapshots is not supported yet")
73-
}
74-
7570
if snapshotSource.GetSnapshotId() == "" {
7671
return nil, status.Error(codes.InvalidArgument, "snapshot ID cannot be empty")
7772
}

0 commit comments

Comments
 (0)