Skip to content

Commit 71b8f58

Browse files
authored
Volume Group Snapshots NAS Implementation
1 parent 0197a4b commit 71b8f58

File tree

10 files changed

+333
-193
lines changed

10 files changed

+333
-193
lines changed

core/orchestrator_core.go

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4191,9 +4191,11 @@ func (o *TridentOrchestrator) CreateGroupSnapshot(
41914191
}
41924192

41934193
if err = currentInfo.Validate(); err != nil {
4194-
return nil, fmt.Errorf("invalid storage target for backend %s", b.Name())
4194+
// If the storage target is invalid, we cannot create a group snapshot, snapshotter should not retry.
4195+
return nil, errors.InvalidInputError("invalid storage target for backend %s", b.Name())
41954196
} else if !currentInfo.IsShared(groupSnapshotTarget) {
4196-
return nil, fmt.Errorf("storage target is not the same for all backends")
4197+
// If the storage target is not shared, we cannot create a group snapshot, snapshotter should not retry.
4198+
return nil, errors.InvalidInputError("storage target is not the same for all backends")
41974199
}
41984200

41994201
// Identify and remove duplicates for any source volumes that are shared across multiple backends.
@@ -4225,6 +4227,7 @@ func (o *TridentOrchestrator) CreateGroupSnapshot(
42254227
}()
42264228

42274229
// Create the group snapshot
4230+
// TODO (TRID-16891): Do post-processing within the driver function; san-economy needs work
42284231
groupSnapshot, snapshots, err = groupSnapshotter.CreateGroupSnapshot(ctx, groupSnapshotConfig, groupSnapshotTarget)
42294232
if err != nil {
42304233
if errors.IsMaxLimitReachedError(err) {
@@ -4234,19 +4237,6 @@ func (o *TridentOrchestrator) CreateGroupSnapshot(
42344237
return nil, fmt.Errorf("failed to create group snapshot %s: %v", groupSnapshotConfig.ID(), err)
42354238
}
42364239

4237-
// Do post-processing here; nas and san should be no-ops; san-economy needs work
4238-
postProcessedSnaps, err := groupSnapshotter.PostProcessGroupSnapshot(ctx, groupSnapshotTarget, groupSnapshot)
4239-
if err != nil {
4240-
return nil, fmt.Errorf("failed to post process group snapshot %s: %v",
4241-
groupSnapshotConfig.ID(), err)
4242-
}
4243-
if postProcessedSnaps != nil {
4244-
// This would be the SAN-Economy case
4245-
for _, postProcessedSnap := range postProcessedSnaps {
4246-
groupSnapshot.SnapshotIDs = append(groupSnapshot.SnapshotIDs, postProcessedSnap.ID())
4247-
}
4248-
}
4249-
42504240
// Save references to new group snapshot and each snapshot
42514241
if err = o.storeClient.AddGroupSnapshot(ctx, groupSnapshot); err != nil {
42524242
return nil, err

frontend/csi/plugin.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,8 @@ func (p *Plugin) getCSIErrorForOrchestratorError(err error) error {
532532
return status.Error(codes.DeadlineExceeded, err.Error())
533533
} else if ok, errPtr := errors.HasResourceExhaustedError(err); ok && errPtr != nil {
534534
return status.Error(codes.ResourceExhausted, err.Error())
535+
} else if errors.IsInvalidInputError(err) {
536+
return status.Error(codes.InvalidArgument, err.Error())
535537
} else {
536538
return status.Error(codes.Unknown, err.Error())
537539
}

mocks/mock_storage/mock_storage.go

Lines changed: 0 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

storage/backend.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,6 @@ type GroupSnapshotter interface {
106106
CreateGroupSnapshot(
107107
ctx context.Context, config *GroupSnapshotConfig, target *GroupSnapshotTargetInfo,
108108
) (*GroupSnapshot, []*Snapshot, error)
109-
PostProcessGroupSnapshot(
110-
ctx context.Context, targetInfo *GroupSnapshotTargetInfo, groupSnapshot *GroupSnapshot,
111-
) ([]*Snapshot, error)
112109
}
113110

114111
// StateGetter provides a common interface for backends that support polling backend for state information.
@@ -1020,18 +1017,6 @@ func (b *StorageBackend) CreateGroupSnapshot(ctx context.Context, config *GroupS
10201017
return snapshotter.CreateGroupSnapshot(ctx, config, target)
10211018
}
10221019

1023-
func (b *StorageBackend) PostProcessGroupSnapshot(
1024-
ctx context.Context, targetInfo *GroupSnapshotTargetInfo, groupSnapshot *GroupSnapshot,
1025-
) ([]*Snapshot, error) {
1026-
snapshotter, ok := b.driver.(GroupSnapshotter)
1027-
if !ok {
1028-
return nil, errors.UnsupportedError(
1029-
fmt.Sprintf("group snapshot is not supported for backend of type %v", b.driver.Name()))
1030-
}
1031-
1032-
return snapshotter.PostProcessGroupSnapshot(ctx, targetInfo, groupSnapshot)
1033-
}
1034-
10351020
const (
10361021
BackendRename = iota
10371022
InvalidVolumeAccessInfoChange

storage_drivers/fake/fake.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,13 +1165,6 @@ func (d *StorageDriver) CreateGroupSnapshot(
11651165
return storage.NewGroupSnapshot(config, snapshotIDs, creationTime), groupedSnapshots, nil
11661166
}
11671167

1168-
func (d *StorageDriver) PostProcessGroupSnapshot(ctx context.Context, targetInfo *storage.GroupSnapshotTargetInfo,
1169-
groupSnapshot *storage.GroupSnapshot,
1170-
) ([]*storage.Snapshot, error) {
1171-
// no-op for fake driver
1172-
return nil, nil
1173-
}
1174-
11751168
func (d *StorageDriver) Get(_ context.Context, name string) error {
11761169
_, ok := d.Volumes[name]
11771170
if !ok {

storage_drivers/ontap/ontap_common.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4058,6 +4058,141 @@ func createFlexvolSnapshot(
40584058
}, nil
40594059
}
40604060

4061+
// GetGroupSnapshotTarget returns a set of information about the target of a group snapshot.
4062+
// This information is used to gather information in a consistent way across storage drivers.
4063+
func GetGroupSnapshotTarget(
4064+
ctx context.Context, volConfigs []*storage.VolumeConfig, config *drivers.OntapStorageDriverConfig,
4065+
client api.OntapAPI,
4066+
) (*storage.GroupSnapshotTargetInfo, error) {
4067+
fields := LogFields{
4068+
"Method": "GetGroupSnapshotTarget",
4069+
"Type": "ontap_common",
4070+
}
4071+
Logd(ctx, config.StorageDriverName,
4072+
config.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> GetGroupSnapshotTarget")
4073+
defer Logd(ctx, config.StorageDriverName,
4074+
config.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< GetGroupSnapshotTarget")
4075+
4076+
targetType := PersonalityUnified
4077+
if config.Flags != nil && config.Flags[FlagPersonality] != "" {
4078+
targetType = config.Flags[FlagPersonality]
4079+
}
4080+
4081+
targetUUID := client.GetSVMUUID()
4082+
4083+
// Construct a set of unique source volume IDs to volume names to configs for the group snapshot.
4084+
targetVolumes := make(storage.GroupSnapshotTargetVolumes, 0)
4085+
for _, volumeConfig := range volConfigs {
4086+
volumeName := volumeConfig.Name
4087+
internalVolName := volumeConfig.InternalName
4088+
4089+
// If the specified volume doesn't exist, return error
4090+
if volExists, err := client.VolumeExists(ctx, internalVolName); err != nil {
4091+
return nil, fmt.Errorf("error checking for existing volume: %v", err)
4092+
} else if !volExists {
4093+
return nil, errors.NotFoundError("volume %s does not exist", internalVolName)
4094+
}
4095+
4096+
if targetVolumes[internalVolName] == nil {
4097+
targetVolumes[internalVolName] = make(map[string]*storage.VolumeConfig)
4098+
}
4099+
4100+
// For other drivers such as the SAN-Eco driver, this will be a flexvol name -> pv name -> volume config.
4101+
targetVolumes[internalVolName][volumeName] = volumeConfig
4102+
}
4103+
4104+
return storage.NewGroupSnapshotTargetInfo(targetType, targetUUID, targetVolumes), nil
4105+
}
4106+
4107+
func CreateGroupSnapshot(
4108+
ctx context.Context, groupSnapshotConfig *storage.GroupSnapshotConfig, target *storage.GroupSnapshotTargetInfo,
4109+
driverConfig *drivers.OntapStorageDriverConfig, client api.OntapAPI, sizeGetter func(context.Context, string) (int, error),
4110+
) (*storage.GroupSnapshot, []*storage.Snapshot, error) {
4111+
fields := LogFields{
4112+
"Method": "CreateGroupSnapshot",
4113+
"Type": "ontap_common",
4114+
}
4115+
Logd(ctx, driverConfig.StorageDriverName,
4116+
driverConfig.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> CreateGroupSnapshot")
4117+
defer Logd(ctx, driverConfig.StorageDriverName,
4118+
driverConfig.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< CreateGroupSnapshot")
4119+
4120+
// Assign an internal name at the driver level.
4121+
groupSnapshotConfig.InternalName = groupSnapshotConfig.ID()
4122+
groupName := groupSnapshotConfig.ID()
4123+
snapName, err := storage.ConvertGroupSnapshotID(groupName)
4124+
if err != nil {
4125+
return nil, nil, err
4126+
}
4127+
4128+
// Track the individual snapshots for each internalVolumeName config in the config snapshot target.
4129+
groupedSnapshots := make([]*storage.Snapshot, 0)
4130+
snapshotIDs := make([]string, 0)
4131+
4132+
// Look at all target source internalVolumes and build a list of snapshots.
4133+
// The sourceVolumeID here correlates to a parent FlexVolume.
4134+
// The list of constituentVolumes is a set of internalVolumes that exist under the source internalVolumeName ID.
4135+
internalVolumes := make([]string, 0, len(target.GetVolumes()))
4136+
for vol := range target.GetVolumes() {
4137+
internalVolumes = append(internalVolumes, vol)
4138+
}
4139+
4140+
// After we create the group snapshot and there is no error,
4141+
// if we fail for another reason we need to return the group snapshot so each snapshot is cleaned up.
4142+
if err = client.ConsistencyGroupSnapshot(ctx, snapName, internalVolumes); err != nil {
4143+
return nil, nil, err
4144+
}
4145+
4146+
var returnErr error
4147+
dateCreated := ""
4148+
for _, internalVolumeName := range internalVolumes {
4149+
snap, err := client.VolumeSnapshotInfo(ctx, snapName, internalVolumeName)
4150+
if err != nil {
4151+
returnErr = errors.Join(returnErr, err)
4152+
}
4153+
if dateCreated == "" {
4154+
dateCreated = snap.CreateTime
4155+
} else if dateCreated != snap.CreateTime {
4156+
Logc(ctx).Debugf("Snapshots in group '%s' created at different times: '%s' vs '%s'",
4157+
groupName, dateCreated, snap.CreateTime)
4158+
}
4159+
4160+
size, err := sizeGetter(ctx, internalVolumeName)
4161+
if err != nil {
4162+
returnErr = errors.Join(returnErr, fmt.Errorf("error reading volume size: %v", err))
4163+
continue
4164+
}
4165+
4166+
var snapVolumeName string
4167+
for volName := range target.GetVolumes()[internalVolumeName] {
4168+
snapVolumeName = volName
4169+
4170+
// Create a snapshot config and object for each constituent snapshot for the group.
4171+
snapConfig := &storage.SnapshotConfig{
4172+
Name: snapName,
4173+
InternalName: snapName,
4174+
VolumeInternalName: internalVolumeName,
4175+
VolumeName: snapVolumeName,
4176+
ImportNotManaged: false,
4177+
GroupSnapshotName: groupName,
4178+
}
4179+
4180+
snapshot := &storage.Snapshot{
4181+
Config: snapConfig,
4182+
Created: snap.CreateTime,
4183+
SizeBytes: int64(size),
4184+
State: storage.SnapshotStateOnline,
4185+
}
4186+
4187+
groupedSnapshots = append(groupedSnapshots, snapshot)
4188+
snapshotIDs = append(snapshotIDs, snapshot.ID())
4189+
}
4190+
}
4191+
4192+
// Create the group snapshot object. This is a logical grouping of the constituent snapshots.
4193+
return storage.NewGroupSnapshot(groupSnapshotConfig, snapshotIDs, dateCreated), groupedSnapshots, returnErr
4194+
}
4195+
40614196
// cloneFlexvol creates a volume clone
40624197
func cloneFlexvol(
40634198
ctx context.Context, cloneVolConfig *storage.VolumeConfig, labels string, split bool, config *drivers.OntapStorageDriverConfig,

storage_drivers/ontap/ontap_nas.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1280,7 +1280,7 @@ func (d *NASStorageDriver) RestoreSnapshot(
12801280
return RestoreSnapshot(ctx, snapConfig, &d.Config, d.API)
12811281
}
12821282

1283-
// DeleteSnapshot creates a snapshot of a volume.
1283+
// DeleteSnapshot deletes a snapshot of a volume.
12841284
func (d *NASStorageDriver) DeleteSnapshot(
12851285
ctx context.Context, snapConfig *storage.SnapshotConfig, _ *storage.VolumeConfig,
12861286
) error {
@@ -1311,6 +1311,34 @@ func (d *NASStorageDriver) DeleteSnapshot(
13111311
return nil
13121312
}
13131313

1314+
// GetGroupSnapshotTarget returns a set of information about the target of a group snapshot.
1315+
// This information is used to gather information in a consistent way across storage drivers.
1316+
func (d *NASStorageDriver) GetGroupSnapshotTarget(
1317+
ctx context.Context, volConfigs []*storage.VolumeConfig,
1318+
) (*storage.GroupSnapshotTargetInfo, error) {
1319+
fields := LogFields{
1320+
"Method": "GetGroupSnapshotTarget",
1321+
"Type": "NASStorageDriver",
1322+
}
1323+
Logd(ctx, d.Name(), d.Config.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> GetGroupSnapshotTarget")
1324+
defer Logd(ctx, d.Name(), d.Config.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< GetGroupSnapshotTarget")
1325+
1326+
return GetGroupSnapshotTarget(ctx, volConfigs, &d.Config, d.API)
1327+
}
1328+
1329+
func (d *NASStorageDriver) CreateGroupSnapshot(
1330+
ctx context.Context, config *storage.GroupSnapshotConfig, target *storage.GroupSnapshotTargetInfo,
1331+
) (*storage.GroupSnapshot, []*storage.Snapshot, error) {
1332+
fields := LogFields{
1333+
"Method": "CreateGroupSnapshot",
1334+
"Type": "NASStorageDriver",
1335+
}
1336+
Logd(ctx, d.Name(), d.Config.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> CreateGroupSnapshot")
1337+
defer Logd(ctx, d.Name(), d.Config.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< CreateGroupSnapshot")
1338+
1339+
return CreateGroupSnapshot(ctx, config, target, &d.Config, d.API, d.volumeUsedSize)
1340+
}
1341+
13141342
// Get tests for the existence of a volume
13151343
func (d *NASStorageDriver) Get(ctx context.Context, name string) error {
13161344
fields := LogFields{"Method": "Get", "Type": "NASStorageDriver"}

storage_drivers/ontap/ontap_nas_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5981,3 +5981,88 @@ func TestOntapStorageDriverGetMirrorTransferTime(t *testing.T) {
59815981
assert.NotNil(t, result, "received nil")
59825982
assert.NoError(t, err, "received error")
59835983
}
5984+
5985+
func TestOntapNasVolumeGroupSnapshot(t *testing.T) {
5986+
ctx := context.Background()
5987+
5988+
mockAPI, driver := newMockOntapNASDriver(t)
5989+
mockAPI.EXPECT().SVMName().AnyTimes().Return("SVM1")
5990+
5991+
groupSnapshotConfig := &storage.GroupSnapshotConfig{
5992+
Name: "groupsnapshot-1234",
5993+
InternalName: "groupsnapshot-1234",
5994+
VolumeNames: []string{"vol1", "vol2"},
5995+
}
5996+
storageVols := storage.GroupSnapshotTargetVolumes{
5997+
"trident_vol1": {
5998+
"vol1": &storage.VolumeConfig{Name: "vol1"},
5999+
},
6000+
"trident_vol2": {
6001+
"vol2": &storage.VolumeConfig{Name: "vol2"},
6002+
},
6003+
}
6004+
targetInfo := &storage.GroupSnapshotTargetInfo{
6005+
StorageType: "unified",
6006+
StorageUUID: "12345",
6007+
StorageVolumes: storageVols,
6008+
}
6009+
storageVolNames := []string{"trident_vol1", "trident_vol2"}
6010+
snapName, _ := storage.ConvertGroupSnapshotID(groupSnapshotConfig.Name)
6011+
snapInfoResult := api.Snapshot{CreateTime: "1"}
6012+
6013+
mockAPI.EXPECT().ConsistencyGroupSnapshot(ctx, snapName, gomock.InAnyOrder(storageVolNames)).Return(nil).Times(1)
6014+
6015+
mockAPI.EXPECT().VolumeSnapshotInfo(ctx, snapName, gomock.Any()).Return(snapInfoResult, nil).Times(2)
6016+
mockAPI.EXPECT().VolumeUsedSize(ctx, gomock.Any()).Return(1073741824, nil).Times(2)
6017+
6018+
groupSnapshot, snapshots, err := driver.CreateGroupSnapshot(ctx, groupSnapshotConfig, targetInfo)
6019+
6020+
assert.Equal(t, groupSnapshot.ID(), groupSnapshotConfig.ID())
6021+
assert.Equal(t, groupSnapshot.GetVolumeNames(), groupSnapshotConfig.GetVolumeNames())
6022+
6023+
for _, snap := range snapshots {
6024+
assert.Contains(t, groupSnapshot.GetSnapshotIDs(), snap.ID())
6025+
}
6026+
6027+
assert.NoError(t, err, "Group snapshot creation failed")
6028+
}
6029+
6030+
func TestOntapNasVolumeGroupTarget(t *testing.T) {
6031+
ctx := context.Background()
6032+
6033+
mockAPI, driver := newMockOntapNASDriver(t)
6034+
mockAPI.EXPECT().SVMName().AnyTimes().Return("SVM1")
6035+
6036+
volumeConfigs := []*storage.VolumeConfig{
6037+
{
6038+
Name: "vol1",
6039+
InternalName: "trident_vol1",
6040+
},
6041+
{
6042+
Name: "vol2",
6043+
InternalName: "trident_vol2",
6044+
},
6045+
}
6046+
6047+
storageVols := storage.GroupSnapshotTargetVolumes{
6048+
"trident_vol1": {
6049+
"vol1": &storage.VolumeConfig{Name: "vol1", InternalName: "trident_vol1"},
6050+
},
6051+
"trident_vol2": {
6052+
"vol2": &storage.VolumeConfig{Name: "vol2", InternalName: "trident_vol2"},
6053+
},
6054+
}
6055+
expectedTargetInfo := &storage.GroupSnapshotTargetInfo{
6056+
StorageType: "Unified",
6057+
StorageUUID: "12345",
6058+
StorageVolumes: storageVols,
6059+
}
6060+
6061+
mockAPI.EXPECT().GetSVMUUID().Return("12345").Times(1)
6062+
mockAPI.EXPECT().VolumeExists(ctx, gomock.Any()).Return(true, nil).Times(2)
6063+
6064+
targetInfo, err := driver.GetGroupSnapshotTarget(ctx, volumeConfigs)
6065+
6066+
assert.Equal(t, targetInfo, expectedTargetInfo)
6067+
assert.NoError(t, err, "Volume group target failed")
6068+
}

0 commit comments

Comments
 (0)