Skip to content

Commit

Permalink
Add CreateVolumeFromSnapshot Functionality for nfs (#226)
Browse files Browse the repository at this point in the history
  • Loading branch information
VamsiSiddu-7 committed Aug 4, 2023
1 parent a6fad25 commit 8107ef1
Show file tree
Hide file tree
Showing 11 changed files with 347 additions and 6 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/dell/dell-csi-extensions/volumeGroupSnapshot v1.2.3-0.20230517135918-9920e636bff1
github.com/dell/gocsi v1.7.0
github.com/dell/gofsutil v1.12.0
github.com/dell/goscaleio v1.11.1-0.20230724122254-8f8b41ad4aad
github.com/dell/goscaleio v1.11.1-0.20230804051431-657fcb1e35a1
github.com/fsnotify/fsnotify v1.5.1
github.com/golang/protobuf v1.5.3
github.com/google/uuid v1.3.0
Expand Down
12 changes: 8 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,14 @@ github.com/dell/gocsi v1.7.0 h1:fMQO2zwAXCaIsUoPCcnnuPMwfQMoaI1/0aqkQVndlxU=
github.com/dell/gocsi v1.7.0/go.mod h1:X/8Ll8qqKAKCenmd1gPJMUvUmgY8cK0LiS8Pck12UaU=
github.com/dell/gofsutil v1.12.0 h1:oo2YHfGFKHvHS1urtqjOIKpaHwcdyqacwKHLXzUg33M=
github.com/dell/gofsutil v1.12.0/go.mod h1:mGMN5grVDtHv2imNw5+gFr2RmCqeyYgBFBldUbHtV78=
github.com/dell/goscaleio v1.11.1-0.20230721055528-55caf6d14be6 h1:xsB1Ergjq3C4DrIhixHfBsoJ8lYj9uY/HsTVqDD9EN8=
github.com/dell/goscaleio v1.11.1-0.20230721055528-55caf6d14be6/go.mod h1:dMTrHnXSsPus+Kd9mrs0JuyrCndoKvFP/bbEdc21Bi8=
github.com/dell/goscaleio v1.11.1-0.20230724122254-8f8b41ad4aad h1:3yLaVslMtLDGrWBkR88F+9tKgJjEKWYrlj7f0aVQd9k=
github.com/dell/goscaleio v1.11.1-0.20230724122254-8f8b41ad4aad/go.mod h1:dMTrHnXSsPus+Kd9mrs0JuyrCndoKvFP/bbEdc21Bi8=
github.com/dell/goscaleio v1.11.1-0.20230707063208-b67372e0f8d0 h1:3GqvTjqCAAG6y8FDUtKRR9q/Uj5lKD1hFD6w7FTzhww=
github.com/dell/goscaleio v1.11.1-0.20230707063208-b67372e0f8d0/go.mod h1:dMTrHnXSsPus+Kd9mrs0JuyrCndoKvFP/bbEdc21Bi8=
github.com/dell/goscaleio v1.11.1-0.20230725041057-0c278cc028bb h1:OY9QG9BlISXVIF29eRyL6Cn3KP/NlfNQVW+UMcqd/WU=
github.com/dell/goscaleio v1.11.1-0.20230725041057-0c278cc028bb/go.mod h1:dMTrHnXSsPus+Kd9mrs0JuyrCndoKvFP/bbEdc21Bi8=
github.com/dell/goscaleio v1.11.1-0.20230801144441-83fb98205f02 h1:o4M+prXXj2k0f53uG+ZL0kOyO36R2J9SM0dVKUseDPs=
github.com/dell/goscaleio v1.11.1-0.20230801144441-83fb98205f02/go.mod h1:dMTrHnXSsPus+Kd9mrs0JuyrCndoKvFP/bbEdc21Bi8=
github.com/dell/goscaleio v1.11.1-0.20230804051431-657fcb1e35a1 h1:Mqxwnv6+BVkIKqwAny+MY1d9JloA1AsazIFmKxfU6yU=
github.com/dell/goscaleio v1.11.1-0.20230804051431-657fcb1e35a1/go.mod h1:dMTrHnXSsPus+Kd9mrs0JuyrCndoKvFP/bbEdc21Bi8=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
Expand Down
81 changes: 81 additions & 0 deletions service/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,14 @@ func (s *service) CreateVolume(
// fetch volume size
size := cr.GetRequiredBytes()

contentSource := req.GetVolumeContentSource()
if contentSource != nil {
snapshotSource := contentSource.GetSnapshot()
if snapshotSource != nil {
Log.Printf("snapshot %s specified as volume content source", snapshotSource.SnapshotId)
return s.createVolumeFromSnapshot(req, snapshotSource, name, size, storagePoolName)
}
}
// log all parameters used in CreateVolume call
fields := map[string]interface{}{
"Name": volName,
Expand Down Expand Up @@ -721,6 +729,15 @@ func (s *service) createVolumeFromSnapshot(req *csi.CreateVolumeRequest,
snapshotSource *csi.VolumeContentSource_SnapshotSource,
name string, sizeInKbytes int64, storagePool string) (*csi.CreateVolumeResponse, error) {

isNFS := false
var fsType string
if len(req.VolumeCapabilities) != 0 {
fsType = req.VolumeCapabilities[0].GetMount().GetFsType()
if fsType == "nfs" {
isNFS = true
}
}

// get systemID from snapshot source CSI id
systemID := s.getSystemIDFromCsiVolumeID(snapshotSource.SnapshotId)
if systemID == "" {
Expand All @@ -732,6 +749,59 @@ func (s *service) createVolumeFromSnapshot(req *csi.CreateVolumeRequest,
"systemID is not found in snapshot source id and there is no default system")
}

if isNFS {
// Look up the snapshot
fmt.Println("snapshotSource.SnapshotId", snapshotSource.SnapshotId)
snapID := getFilesystemIDFromCsiVolumeID(snapshotSource.SnapshotId)
srcVol, err := s.getFilesystemByID(snapID, systemID)

if err != nil {
return nil, status.Errorf(codes.NotFound, "Snapshot not found: %s", snapshotSource.SnapshotId)
}

// Validate the size is the same.
if int64(srcVol.SizeTotal) != sizeInKbytes {
return nil, status.Errorf(codes.InvalidArgument,
"Snapshot %s has incompatible size %d bytes with requested %d bytes",
snapshotSource.SnapshotId, srcVol.SizeTotal, sizeInKbytes)
}

system := s.systems[systemID]

// Validate the storagePool is the same.
snapStoragePool := s.getStoragePoolNameFromID(systemID, srcVol.StoragePoolID)
if snapStoragePool != storagePool {
return nil, status.Errorf(codes.InvalidArgument,
"Snapshot storage pool %s is different than the requested storage pool %s", snapStoragePool, storagePool)
}

_, err = system.RestoreFileSystemFromSnapshot(&siotypes.RestoreFsSnapParam{
SnapshotID: snapID,
}, srcVol.ParentID)

if err != nil {
return nil, status.Errorf(codes.Internal, "error during fs creation from snapshot: %s", snapshotSource.SnapshotId)
}

restoreFs, err := system.GetFileSystemByIDName(srcVol.ParentID, "")

if err != nil {
if strings.Contains(err.Error(), sioGatewayFileSystemNotFound) {
return nil, status.Errorf(codes.NotFound, "filesystem not found: %s", srcVol.ID)
}
}

csiVolume := s.getCSIVolumeFromFilesystem(restoreFs, systemID)

csiVolume.ContentSource = req.GetVolumeContentSource()
copyInterestingParameters(req.GetParameters(), csiVolume.VolumeContext)

Log.Printf("Volume (from snap) %s (%s) storage pool %s",
csiVolume.VolumeContext["Name"], csiVolume.VolumeId, csiVolume.VolumeContext["StoragePoolName"])
return &csi.CreateVolumeResponse{Volume: csiVolume}, nil

}

// Look up the snapshot
snapID := getVolumeIDFromCsiVolumeID(snapshotSource.SnapshotId)
srcVol, err := s.getVolByID(snapID, systemID)
Expand Down Expand Up @@ -920,6 +990,17 @@ func (s *service) DeleteVolume(
}
}

listSnaps, err := system.GetFsSnapshotsByVolumeID(fsID)
if err != nil {
return nil, status.Errorf(codes.Unknown, "failure getting snapshot: %s", err.Error())
}

if len(listSnaps) > 0 {
return nil, status.Errorf(codes.FailedPrecondition,
"unable to delete NFS volume -- snapshots based on this volume still exist: %v",
listSnaps)
}

fsName := toBeDeletedFS.Name

// Check if nfs export exists for the File system
Expand Down
13 changes: 12 additions & 1 deletion service/features/delete_volume.feature
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,18 @@ Feature: VxFlex OS CSI interface
Then a valid PublishVolumeResponse is returned
And I call DeleteVolume nfs with "single-writer"
Then the error contains "can not be deleted as it has associated NFS Export"


Scenario: a Basic Nfs delete FileSystem with Snapshot
Given a VxFlexOS service
When I call Probe
And I specify CreateVolumeMountRequest "nfs"
And I call CreateVolume "volume1"
Then a valid CreateVolumeResponse is returned
And I call CreateSnapshot NFS "snap1"
And no error was received
And I call DeleteVolume nfs with "single-writer"
Then the error contains "unable to delete NFS volume -- snapshots based on this volume still exist"

Scenario: Test Idempotent Basic nfs delete FileSystem
Given a VxFlexOS service
When I call Probe
Expand Down
82 changes: 82 additions & 0 deletions service/features/service.feature
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,88 @@ Feature: VxFlex OS CSI interface
Then a valid CreateVolumeResponse is returned
And no error was received

Scenario: Create a volume from a snapshot NFS no error
Given a VxFlexOS service
And I call Probe
And I specify CreateVolumeMountRequest "nfs"
And I call CreateVolume "volume1"
Then a valid CreateVolumeResponse is returned
And I call CreateSnapshot NFS "snap1"
And no error was received
When I call Probe
And I call Create Volume from SnapshotNFS
Then a valid CreateVolumeResponse is returned
And no error was received

Scenario: Idempotent create a volume from a snapshot no error
Given a VxFlexOS service
And I call Probe
And I specify CreateVolumeMountRequest "nfs"
And I call CreateVolume "volume1"
Then a valid CreateVolumeResponse is returned
And I call CreateSnapshot NFS "snap1"
And no error was received
When I call Probe
And I call Create Volume from SnapshotNFS
Then a valid CreateVolumeResponse is returned
And no error was received
When I call Probe
And I call Create Volume from SnapshotNFS
Then a valid CreateVolumeResponse is returned
And no error was received

Scenario: Create a volume from a snapshot NFS snapshot not found error
Given a VxFlexOS service
And I call Probe
And I specify CreateVolumeMountRequest "nfs"
And I call CreateVolume "volume1"
Then a valid CreateVolumeResponse is returned
And I call CreateSnapshot NFS "snap1"
And no error was received
When I call Probe
And I induce error "GetFileSystemsByIdError"
And I call Create Volume from SnapshotNFS
Then the error contains "Snapshot not found"

Scenario: Create a volume from a snapshot NFS incompatible size error
Given a VxFlexOS service
And I call Probe
And I specify CreateVolumeMountRequest "nfs"
And I call CreateVolume "volume1"
Then a valid CreateVolumeResponse is returned
And I call CreateSnapshot NFS "snap1"
And no error was received
And the wrong capacity
When I call Probe
And I call Create Volume from SnapshotNFS
Then the error contains "incompatible size"

Scenario: Create a volume from a snapshot NFS different storage pool error
Given a VxFlexOS service
And I call Probe
And I specify CreateVolumeMountRequest "nfs"
And I call CreateVolume "volume1"
Then a valid CreateVolumeResponse is returned
And I call CreateSnapshot NFS "snap1"
And no error was received
And the wrong storage pool
When I call Probe
And I call Create Volume from SnapshotNFS
Then the error contains "different than the requested storage pool"

Scenario: Create a volume from a snapshot NFS restoreVolumeError
Given a VxFlexOS service
And I call Probe
And I specify CreateVolumeMountRequest "nfs"
And I call CreateVolume "volume1"
Then a valid CreateVolumeResponse is returned
And I call CreateSnapshot NFS "snap1"
And no error was received
When I call Probe
And I induce error "restoreVolumeError"
And I call Create Volume from SnapshotNFS
Then the error contains "error during fs creation from snapshot"

Scenario: Create a volume from a snapshot with wrong capacity
Given a VxFlexOS service
And a valid snapshot
Expand Down
21 changes: 21 additions & 0 deletions service/step_defs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3589,6 +3589,26 @@ func (f *feature) iCallCreateVolumeFromSnapshot() error {
return nil
}

func (f *feature) iCallCreateVolumeFromSnapshotNFS() error {
ctx := new(context.Context)
req := getTypicalNFSCreateVolumeRequest()
req.Name = "volumeFromSnap"
if f.wrongCapacity {
req.CapacityRange.RequiredBytes = 64 * 1024 * 1024 * 1024
}
if f.wrongStoragePool {
req.Parameters["storagepool"] = "other_storage_pool"
}
source := &csi.VolumeContentSource_SnapshotSource{SnapshotId: "14dbbf5617523654" + "/" + fileSystemNameToID["snap1"]}
req.VolumeContentSource = new(csi.VolumeContentSource)
req.VolumeContentSource.Type = &csi.VolumeContentSource_Snapshot{Snapshot: source}
f.createVolumeResponse, f.err = f.service.CreateVolume(*ctx, req)
if f.err != nil {
fmt.Printf("Error on CreateVolume from snap: %s\n", f.err.Error())
}
return nil
}

func (f *feature) theWrongCapacity() error {
f.wrongCapacity = true
return nil
Expand Down Expand Up @@ -4501,6 +4521,7 @@ func FeatureContext(s *godog.ScenarioContext) {
s.Step(`^I call DeleteSnapshot NFS$`, f.iCallDeleteSnapshotNFS)
s.Step(`^a valid snapshot consistency group$`, f.aValidSnapshotConsistencyGroup)
s.Step(`^I call Create Volume from Snapshot$`, f.iCallCreateVolumeFromSnapshot)
s.Step(`^I call Create Volume from SnapshotNFS$`, f.iCallCreateVolumeFromSnapshotNFS)
s.Step(`^the wrong capacity$`, f.theWrongCapacity)
s.Step(`^the wrong storage pool$`, f.theWrongStoragePool)
s.Step(`^there are (\d+) valid snapshots of "([^"]*)" volume$`, f.thereAreValidSnapshotsOfVolume)
Expand Down
35 changes: 35 additions & 0 deletions service/step_handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ func getRouter() http.Handler {
scaleioRouter.HandleFunc("/rest/v1/nfs-exports", handleNFSExports)
scaleioRouter.HandleFunc("/rest/v1/file-systems/{id}", handleGetFileSystems)
scaleioRouter.HandleFunc("/rest/v1/nfs-exports/{id}", handleGetNFSExports)
scaleioRouter.HandleFunc("/rest/v1/file-systems/{id}/restore", handleRestoreSnapshotNFS)
scaleioRouter.HandleFunc("/rest/v1/file-interfaces/{id}", handleGetFileInterface)
scaleioRouter.HandleFunc("/rest/v1/file-systems/{id}/snapshot", handleNFSSnapshots)
scaleioRouter.HandleFunc("/api/types/Volume/instances", handleVolumeInstances)
Expand Down Expand Up @@ -819,6 +820,40 @@ func handleFileSystems(w http.ResponseWriter, r *http.Request) {
}
}

func handleRestoreSnapshotNFS(w http.ResponseWriter, r *http.Request) {

switch r.Method {

// Post is CreateVolume; here just return a volume id encoded from the name
case http.MethodPost:
if inducedError.Error() == "restoreVolumeError" {
writeError(w, "create volume induced error", http.StatusRequestTimeout, codes.Internal)
return
}

req := types.RestoreFsSnapParam{}
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&req)
if err != nil {
log.Printf("error decoding json: %s\n", err.Error())
}

// good response
resp := new(types.RestoreFsSnapResponse)
resp.ID = req.SnapshotID
if debug {
log.Printf("response id: %s\n", resp.ID)
}
encoder := json.NewEncoder(w)
err = encoder.Encode(resp)
if err != nil {
log.Printf("error encoding json: %s\n", err.Error())
}

log.Printf("end make restore fs from snaspshot")
}
}

func handleGetFileSystems(w http.ResponseWriter, r *http.Request) {
switch r.Method {

Expand Down
9 changes: 9 additions & 0 deletions test/helm/1vols+restore-nfs/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: 1vols+restore-nfs
version: 1.0.0
appVersion: v1
description: |
Tests VxFlexOS CSI deployments.
keywords:
- vxflexos-csi
- storage
engine: gotpl
16 changes: 16 additions & 0 deletions test/helm/1vols+restore-nfs/templates/createFromSnap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: restorepvc
namespace: helmtest-vxflexos
spec:
storageClassName: vxflexos-nfs
dataSource:
name: pvol0-snap1
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
33 changes: 33 additions & 0 deletions test/helm/1vols+restore-nfs/templates/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: vxflextest
namespace: helmtest-vxflexos
---
kind: StatefulSet
apiVersion: apps/v1
metadata:
name: vxflextest
namespace: helmtest-vxflexos
spec:
selector:
matchLabels:
app: vxflextest
serviceName: 2vols
template:
metadata:
labels:
app: vxflextest
spec:
serviceAccount: vxflextest
containers:
- name: test
image: docker.io/centos:latest
command: [ "/bin/sleep", "3600" ]
volumeMounts:
- mountPath: "/data0"
name: pvol0
volumes:
- name: pvol0
persistentVolumeClaim:
claimName: restorepvc
Loading

0 comments on commit 8107ef1

Please sign in to comment.